Programovanie

Postupujte podľa reťazca zodpovednosti

Nedávno som prešiel na Mac OS X z Windows a z výsledkov som nadšený. Ale potom som opäť strávil iba krátky päťročný pobyt vo Windows NT a XP; predtým som bol 15 rokov striktne vývojárom v Unixe, väčšinou na strojoch Sun Microsystems. Tiež som mal to šťastie, aby som vyvinul softvér v rámci Nextstepu, bujného predchodcu Unixu pre Mac OS X, takže som trochu zaujatý.

Okrem krásneho používateľského rozhrania systému Aqua je Mac OS X Unix, ktorý je pravdepodobne najlepším operačným systémom, aký existuje. Unix má veľa skvelých funkcií; jedným z najznámejších je rúra, ktorý umožňuje vytvárať kombinácie príkazov pipetovaním výstupu jedného príkazu na vstup iného. Predpokladajme napríklad, že chcete uviesť zoznam zdrojových súborov z distribucie zdrojov Struts, ktoré vyvolávajú alebo definujú pomenovanú metódu vykonať (). Tu je jeden spôsob, ako to urobiť pomocou fajky:

 grep "spustiť (" "nájsť $ STRUTS_SRC_DIR -name" * .java "" | awk -F: '{print}' 

The grep príkaz vyhľadáva v regulárnych výrazoch súbory; tu ho používam na vyhľadanie výskytov reťazca vykonať ( v súboroch objavených Nájsť príkaz. grepvýstup je privádzaný do potrubia awk, ktorý vytlačí prvý token - ohraničený dvojbodkou - v každom riadku grepvýstup (zvislá čiara označuje rúrku). Ten token je názov súboru, takže skončím so zoznamom názvov súborov, ktoré obsahujú reťazec vykonať (.

Teraz, keď mám zoznam názvov súborov, môžem na zoradenie zoznamu použiť inú fajku:

 grep "execute (" "nájsť $ STRUTS_SRC_DIR -name" * .java "" | awk -F: '{print}' | triediť

Tentokrát som prepísal zoznam mien súborov do triediť. Čo ak chcete vedieť, koľko súborov obsahuje reťazec vykonať (? S iným potrubím je to jednoduché:

 grep "execute (" "nájsť $ STRUTS_SRC_DIR -name" * .java "" | awk -F: '{print}' | sort -u | wc -l 

The wc príkaz počíta slová, riadky a bajty. V tomto prípade som špecifikoval -l možnosť počítať riadky, jeden riadok pre každý súbor. Pridal som aj a -u možnosť triediť aby sa zabezpečila jedinečnosť každého názvu súboru ( -u možnosť odfiltruje duplikáty).

Rúry sú silné, pretože vám umožňujú dynamicky vytvárať reťaz operácií. Softvérové ​​systémy často využívajú ekvivalent rúrok (napr. E-mailové filtre alebo sada filtrov pre servlet). Srdcom rúr a filtrov je návrhový vzor: Chain of Responsibility (VR).

Poznámka: Zdrojový kód tohto článku si môžete stiahnuť zo zdrojov.

Úvod VR

Vzor Chain of Responsibility používa reťazec objektov na vybavenie žiadosti, ktorou je zvyčajne udalosť. Objekty v reťazci poslali požiadavku pozdĺž reťaze, kým jeden z objektov nespracuje udalosť. Spracovanie sa zastaví po spracovaní udalosti.

Obrázok 1 zobrazuje, ako vzor VR spracováva žiadosti.

V Dizajnové vzory, autori popisujú reťazec zodpovednosti takto:

Dajte pozor, aby ste odosielateľa žiadosti nepripojili k prijímaču. Dajte viac ako jednému objektu príležitosť žiadosť vybaviť. Prijímané objekty pripútajte reťazou a požiadavku odovzdajte pozdĺž reťaze, kým s nimi objekt nespracuje.

Vzor reťazca zodpovednosti je použiteľný, ak:

  • Chcete oddeliť odosielateľa a príjemcu požiadavky
  • Viaceré objekty určené za behu aplikácie sú kandidátmi na spracovanie žiadosti
  • Nechcete vo svojom kóde výslovne špecifikovať obslužné rutiny

Ak používate vzor CoR, nezabudnite:

  • Iba jeden objekt v reťazci vybavuje požiadavku
  • Niektoré žiadosti nemusia byť vybavené

Tieto obmedzenia samozrejme platia pre klasickú implementáciu VR. V praxi sú tieto pravidlá ohnuté; napríklad filtre servletov sú implementáciou VR, ktorá umožňuje viacerým filtrom spracovať požiadavku HTTP.

Obrázok 2 zobrazuje diagram triedy vzorov CoR.

Typicky sú obslužné rutiny požiadaviek rozšírenia základnej triedy, ktorá udržuje odkaz na nasledujúceho obslužného programu v reťazci, ktorý sa nazýva nástupca. Môže byť implementovaná základná trieda handleRequest () Páči sa ti to:

 verejná abstraktná trieda HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (successor! = null) successor.handleRequest (sro); }} 

Štandardne teda obslužní pracovníci odovzdajú požiadavku ďalšiemu obsluhovateľovi v reťazci. Betónová prístavba HandlerBase môže vyzerať takto:

 verejná trieda SpamFilter rozširuje HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Ak je správa spam // vykonajte akciu súvisiacu so spamom. Neposielať ďalej správy. } else {// Správa nie je spam. super.handleRequest (mailMessage); // Odošle správu ďalšiemu filtru v reťazci. }}} 

The SpamFilter vybaví požiadavku (pravdepodobne prijatie nového e-mailu), ak je správa spam, a preto požiadavka už nejde; inak sa dôveryhodné správy odošlú ďalšiemu obsluhovateľovi, pravdepodobne ďalšiemu e-mailovému filtru, ktorý sa ich snaží vyradiť. Posledný filter v reťazci môže nakoniec správu uložiť po jej zhromaždení pohybom cez niekoľko filtrov.

Upozorňujeme, že hypotetické e-mailové filtre, o ktorých sa diskutuje vyššie, sa navzájom vylučujú: Žiadosť nakoniec vybaví iba jeden filter. Môžete sa rozhodnúť obrátiť to naruby tým, že necháte viacero filtrov spracovať jednu požiadavku, čo je lepšia analógia k Unixovým rúram. Či tak alebo onak, základným motorom je model CoR.

V tomto článku pojednávam o dvoch implementáciách vzorov Chain of Responsibility: servletové filtre, populárna implementácia CoR, ktorá umožňuje spracovanie viacerých filtrov, a pôvodný model udalosti Abstract Window Toolkit (AWT), nepopulárna klasická implementácia VR, ktorá bola nakoniec zastaraná. .

Servletové filtre

V začiatkoch začiatkov platformy Java 2 Platform Enterprise Edition (J2EE) poskytovali niektoré kontajnery servletov užitočnú funkciu známu ako reťazenie servletov, pomocou ktorej bolo možné na servlet v podstate použiť zoznam filtrov. Filtre servletov sú populárne, pretože sú užitočné z hľadiska zabezpečenia, kompresie, protokolovania a ďalších. A samozrejme, môžete zostaviť reťazec filtrov, ktoré urobia niektoré alebo všetky tieto veci v závislosti od prevádzkových podmienok.

S príchodom špecifikácie Java Servlet verzie 2.3 sa filtre stali štandardnými súčasťami. Na rozdiel od klasického CoR filtre servletov umožňujú vybaviť žiadosť viacerými objektmi (filtrami) v reťazci.

Servletové filtre sú silným doplnkom k J2EE. Z hľadiska návrhových vzorov tiež poskytujú zaujímavý zvrat: Ak chcete upraviť požiadavku alebo odpoveď, okrem VR použijete aj vzor Decorator. Obrázok 3 zobrazuje fungovanie filtrov servletu.

Jednoduchý servletový filter

Na filtrovanie servletu musíte urobiť tri veci:

  • Implementujte servlet
  • Implementujte filter
  • Priraďte filter a servlet

Príklady 1 až 3 vykonávajú všetky tri kroky za sebou:

Príklad 1. Servlet

import java.io.PrintWriter; import javax.servlet. *; import javax.servlet.http. *; public class FilteredServlet extends HttpServlet {public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException {PrintWriter out = response.getWriter (); out.println ("Vyvolaný filtrovaný servlet"); }} 

Príklad 2. Filter

import java.io.PrintWriter; import javax.servlet. *; import javax.servlet.http.HttpServletRequest; verejná trieda AuditFilter implementuje filter {private ServletContext app = null; public void init (konfigurácia FilterConfig) {app = config.getServletContext (); } verejná neplatnosť doFilter(Požiadavka ServletRequest, odpoveď ServletResponse, reťazec FilterChain) hodí java.io.IOException, javax.servlet.ServletException {app.log (((HttpServletRequest) požiadavka) .getServletPath ()); reťaz.dofiltrovať(žiadosť, odpoveď); } public void zničiť () {}} 

Príklad 3. Deskriptor nasadenia

    auditFilter AuditFilter <mapovanie filtra>auditFilter/filtrovanýServlet</ mapovanie filtra>filtrovanýServlet FiltrovanýServlet filtrovanýServlet / filtrovanýServlet ... 

Ak pristupujete k servletu pomocou adresy URL /filtrovanýServlet, auditFilter na požiadanie pred servletom dostane crack. AuditFilter.doFilter zapíše do protokolového súboru kontajnera servletu a zavolá chain.doFilter () postúpiť žiadosť. Filtre na servlet sa nevyžadujú chain.doFilter (); ak tak neurobia, požiadavka sa nepreposiela. Môžem pridať ďalšie filtre, ktoré by boli vyvolané v poradí, v akom sú deklarované v predchádzajúcom súbore XML.

Teraz, keď ste videli jednoduchý filter, pozrime sa na ďalší filter, ktorý upravuje odpoveď HTTP.

Filtrujte odpoveď podľa vzoru Decorator

Na rozdiel od predchádzajúceho filtra musia niektoré filtre servletov upraviť požiadavku alebo odpoveď HTTP. Je zaujímavé, že táto úloha zahŕňa vzor dekoratérov. Vzorec dekoratéra som diskutoval v dvoch predchádzajúcich Dizajnové vzory Java články: „Ohromte svojich priateľov vývojárov dizajnovými vzormi“ a „Ozdobte svoj kód Java“.

V príklade 4 je uvedený filter, ktorý vykonáva jednoduché hľadanie a nahradenie v tele odpovede. Tento filter zdobí reakciu servletu a odovzdáva dekorátor servletu. Keď servlet dokončí zápis do zdobenej odpovede, filter vykoná vyhľadávanie a nahradenie v rámci obsahu odpovede.

Príklad 4. Filter vyhľadávania a výmeny

import java.io. *; import javax.servlet. *; import javax.servlet.http. *; verejná trieda SearchAndReplaceFilter implementuje Filter {private FilterConfig config; public void init (konfigurácia FilterConfig) {this.config = config; } public FilterConfig getFilterConfig () {return config; } public void doFilter (požiadavka ServletRequest, odpoveď ServletResponse, reťazec FilterChain) hodí java.io.IOException, javax.servlet.ServletException {StringWrapper wrapper = nový StringWrapper((HttpServletResponse) odpoveď); reťaz.dofiltrovať(žiadosť, zavinovačka); Reťazec responseString = wrapper.toString(); Reťazec search = config.getInitParameter ("hľadanie"); Reťazec replace = config.getInitParameter ("nahradiť"); if (search == null || replace == null) return; // Parametre nie sú správne nastavené int index = responseString.indexOf (search); if (index! = -1) {String beforeReplace = responseString.substring (0, index); Reťazec afterReplace = responseString.substring (index + search.length ()); response.getWriter (). tlač(beforeReplace + replace + afterReplace); }} public void destru () {config = null; }} 

Predchádzajúci filter vyhľadáva pomenované parametre inicializácie filtra Vyhľadávanie a vymeniť; ak sú definované, filter nahradí prvý výskyt súboru Vyhľadávanie hodnota parametra s vymeniť hodnota parametra.

SearchAndReplaceFilter.doFilter () zabalí (alebo ozdobí) objekt odpovede obalom (dekorátorom), ktorý predstavuje odpoveď. Kedy SearchAndReplaceFilter.doFilter () hovory chain.doFilter () na preposlanie požiadavky sa namiesto pôvodnej odpovede odovzdá obálka. Požiadavka sa preposiela na servlet, ktorý generuje odpoveď.

Kedy chain.doFilter () vráti, servlet je hotový s požiadavkou, tak idem do práce. Najprv skontrolujem, či Vyhľadávanie a vymeniť parametre filtra; ak je prítomný, získam reťazec spojený s obalom odpovede, čo je obsah odpovede. Potom vykonám zámenu a vytlačím ju späť na odpoveď.

Príklad 5 uvádza zoznam StringWrapper trieda.

Príklad 5. Dekoratér

import java.io. *; import javax.servlet. *; import javax.servlet.http. *; verejná trieda StringWrapper rozširuje HttpServletResponseWrapper {spisovateľ StringWriter = nový StringWriter (); public StringWrapper (HttpServletResponse response) {super (odpoveď); } public PrintWriter getWriter () {vrátiť nový PrintWriter (zapisovač); } public String toString () {return writer.toString (); }} 

StringWrapper, ktorý zdobí odpoveď HTTP v príklade 4, je rozšírením HttpServletResponseWrapper, čo nás šetrí prácou na vytváraní základnej triedy dekoratérov na zdobenie odpovedí HTTP. HttpServletResponseWrapper nakoniec implementuje ServletResponse rozhranie, takže príklady HttpServletResponseWrapper môžu byť odovzdané akejkoľvek metóde, ktorá očakáva a ServletResponse objekt. Preto SearchAndReplaceFilter.doFilter () môže volať chain.doFilter (požiadavka, zavinovačka) namiesto chain.doFilter (požiadavka, odpoveď).

Teraz, keď máme filter a obálku odpovedí, spojme filter so vzorom adresy URL a určme vzory hľadania a nahradenia:

$config[zx-auto] not found$config[zx-overlay] not found