Programování

Postupujte podle řetězce odpovědnosti

Nedávno jsem přešel na Mac OS X z Windows a výsledky mě nadchly. Ale znovu jsem strávil jen krátké pětileté působení ve Windows NT a XP; předtím jsem byl 15 let striktně vývojářem Unixu, většinou na strojích Sun Microsystems. Také jsem měl to štěstí, že jsem vyvinul software v rámci Nextstepu, svěžího předchůdce Unixu pro Mac OS X, takže jsem trochu zaujatý.

Kromě krásného uživatelského rozhraní Aqua je Mac OS X Unix, pravděpodobně nejlepší operační systém, jaký existuje. Unix má mnoho skvělých funkcí; jedním z nejznámějších je trubka, který umožňuje vytvářet kombinace příkazů pipetováním výstupu jednoho příkazu na vstup jiného. Předpokládejme například, že chcete vypsat zdrojové soubory z distribuce zdrojů Struts, které vyvolávají nebo definují pojmenovanou metodu vykonat(). Tady je jeden způsob, jak to udělat pomocí potrubí:

 grep "execute (" "najít $ STRUTS_SRC_DIR -name" * .java "" | awk -F: '{print}' 

The grep příkaz vyhledá v regulárních výrazech soubory; zde jej používám k vyhledání výskytů řetězce vykonat( v souborech objevených nalézt příkaz. grepDo kanálu je vložen výstup awk, který vytiskne první token - ohraničený dvojtečkou - v každém řádku grepvýstup (svislá čára označuje rouru). Ten token je název souboru, takže skončím se seznamem názvů souborů, které obsahují řetězec vykonat(.

Teď, když mám seznam názvů souborů, mohu použít další potrubí k seřazení seznamu:

 grep "spustit (" "najít $ STRUTS_SRC_DIR -name" * .java "" | awk -F: '{tisk}' | třídit

Tentokrát jsem pipoval seznam názvů souborů třídit. Co když chcete vědět, kolik souborů obsahuje řetězec vykonat(? S jinou trubkou je to snadné:

 grep "execute (" "najít $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}' | sort -u | wc -l 

The toaleta příkaz počítá slova, řádky a bajty. V tomto případě jsem specifikoval -l možnost počítat řádky, jeden řádek pro každý soubor. Také jsem přidal a -u možnost třídit zajistit jedinečnost pro každý název souboru ( -u možnost odfiltruje duplikáty).

Potrubí jsou silné, protože vám umožňují dynamicky sestavovat řetězec operací. Softwarové systémy často používají ekvivalent potrubí (např. E-mailové filtry nebo sadu filtrů pro servlet). Srdcem trubek a filtrů je designový vzor: Chain of Responsibility (VR).

Poznámka: Zdrojový kód tohoto článku si můžete stáhnout ze zdrojů.

Úvod VR

Vzor Chain of Responsibility používá řetězec objektů ke zpracování požadavku, což je obvykle událost. Objekty v řetězci předávají požadavek podél řetězce, dokud jeden z objektů nezpracová událost. Zpracování se zastaví po zpracování události.

Obrázek 1 ukazuje, jak vzor VR zpracovává požadavky.

v Designové vzory, autoři popisují vzor Chain of Responsibility takto:

Vyhněte se spojování odesílatele požadavku s jeho příjemcem tím, že dáte více než jednomu objektu šanci tento požadavek zpracovat. Zřetězit přijímající objekty a předat požadavek podél řetězce, dokud s ním objekt nezpracuje.

Vzor řetězce odpovědnosti je použitelný, pokud:

  • Chcete oddělit odesílatele a příjemce žádosti
  • Více objektů, které jsou určeny za běhu, jsou kandidáty na zpracování požadavku
  • Nechcete ve svém kódu explicitně specifikovat obslužné rutiny

Pokud používáte vzor VR, nezapomeňte:

  • Pouze jeden objekt v řetězci zpracovává požadavek
  • Některé žádosti nemusí být vyřízeny

Tato omezení samozřejmě platí pro klasickou implementaci VR. V praxi se tato pravidla ohýbají; například filtry servletů jsou implementací VR, která umožňuje více filtrům zpracovat požadavek HTTP.

Obrázek 2 ukazuje diagram tříd VR.

Obslužné rutiny požadavků jsou obvykle rozšíření základní třídy, která udržuje odkaz na další obslužnou rutinu v řetězci, známou jako nástupce. Může se implementovat základní třída handleRequest () takhle:

 public abstract class HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (successor! = null) successor.handleRequest (sro); }} 

Ve výchozím nastavení tedy obslužné rutiny předají požadavek dalšímu obslužnému programu v řetězci. Konkrétní rozšíření HandlerBase může vypadat takto:

 veřejná třída SpamFilter rozšiřuje HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Pokud je zpráva spam //, proveďte akci související se spamem. Nepředávejte zprávu. } else {// Zpráva není spam. super.handleRequest (mailMessage); // Předání zprávy dalšímu filtru v řetězci. }}} 

The SpamFilter vyřídí požadavek (pravděpodobně přijetí nového e-mailu), pokud je zpráva spam, a proto požadavek dále nepokračuje; jinak se důvěryhodné zprávy předají dalšímu obslužnému programu, pravděpodobně dalšímu e-mailovému filtru, který je chce vyřadit. Nakonec může poslední filtr v řetězci uložit zprávu poté, co projde shromážděním pohybem několika filtrů.

Výše uvedené hypotetické e-mailové filtry se vzájemně vylučují: Nakonec požadavek zpracovává pouze jeden filtr. Můžete se rozhodnout to obrátit naruby tím, že necháte více filtrů zpracovat jeden požadavek, což je lepší analogie k Unixovým kanálům. Ať tak či onak, základním motorem je vzor VR.

V tomto článku pojednávám o dvou implementacích vzorů Chain of Responsibility: servletové filtry, populární implementace VR, která umožňuje zpracovat požadavek více filtrů, a původní model události Abstract Window Toolkit (AWT), nepopulární klasická implementace VR, která byla nakonec zastaralá .

Servletové filtry

V počátečních dobách platformy Java 2 Platform, Enterprise Edition (J2EE) poskytovaly některé kontejnery servletů užitečnou funkci známou jako řetězení servletů, přičemž na servlet bylo možné v podstatě použít seznam filtrů. Filtry servletů jsou oblíbené, protože jsou užitečné pro zabezpečení, kompresi, protokolování a další. A samozřejmě můžete sestavit řetězec filtrů, které provedou některé nebo všechny tyto věci v závislosti na podmínkách běhu.

S příchodem specifikace Java Servlet Specification verze 2.3 se filtry staly standardními součástmi. Na rozdíl od klasického VR umožňují filtry servletů zpracovat požadavek více objektům (filtrům) v řetězci.

Filtry servletů jsou výkonným doplňkem J2EE. Z hlediska návrhových vzorů také poskytují zajímavý zvrat: Pokud chcete upravit požadavek nebo odpověď, použijete kromě VR také dekorační vzor. Obrázek 3 ukazuje, jak fungují filtry servletů.

Jednoduchý servletový filtr

Pro filtrování servletu musíte udělat tři věci:

  • Implementujte servlet
  • Implementujte filtr
  • Přiřaďte filtr a servlet

Příklady 1-3 provedou postupně všechny tři kroky:

Příklad 1. Servlet

import java.io.PrintWriter; importovat javax.servlet. *; importovat 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 ("Vyvolán filtrovaný servlet"); }} 

Příklad 2. Filtr

import java.io.PrintWriter; importovat javax.servlet. *; import javax.servlet.http.HttpServletRequest; veřejná třída AuditFilter implementuje filtr {private ServletContext app = null; public void init (konfigurace FilterConfig) {app = config.getServletContext (); } public void doFilter(Požadavek ServletRequest, odpověď ServletResponse, řetězec FilterChain) hodí java.io.IOException, javax.servlet.ServletException {app.log (((HttpServletRequest) požadavek) .getServletPath ()); řetěz.dofiltrovat(vyžádat odpověď); } public void zničit () {}} 

Příklad 3. Deskriptor nasazení

    auditFilter AuditFilter <mapování filtrů>auditFilter/filtrovanýServlet</ mapování filtrů>filtrovanýServlet FiltrovanýServlet filtrovanýServlet / filtrovanýServlet ... 

Pokud přistupujete k servletu pomocí adresy URL /filtrovanýServletauditFilter dostane crack na žádost před servletem. AuditFilter.doFilter zapisuje do souboru protokolu kontejneru servletu a volá chain.doFilter () předat žádost. Filtry servletů nejsou při volání vyžadovány chain.doFilter (); pokud ne, požadavek není předán. Mohu přidat další filtry, které by byly vyvolány v pořadí, v jakém jsou deklarovány v předchozím souboru XML.

Nyní, když jste viděli jednoduchý filtr, pojďme se podívat na jiný filtr, který upravuje odpověď HTTP.

Filtrujte odpověď vzorem dekoratéru

Na rozdíl od předchozího filtru je třeba, aby některé filtry servletů upravily požadavek nebo odpověď HTTP. Je zajímavé, že tento úkol zahrnuje vzor dekoratérů. Mluvil jsem o vzoru Dekoratér ve dvou předchozích Java designové vzory články: „Ohromte své přátele vývojářů designovými vzory“ a „Ozdobte svůj kód Java“.

Příklad 4 uvádí filtr, který provádí jednoduché hledání a nahrazení v těle odpovědi. Tento filtr zdobí odezvu servletu a předává dekorátor servletu. Když servlet dokončí zápis do zdobené odpovědi, filtr provede hledání a nahrazení v rámci obsahu odpovědi.

Příklad 4. Filtr hledání a nahrazení

importovat java.io. *; importovat javax.servlet. *; importovat javax.servlet.http. *; veřejná třída SearchAndReplaceFilter implementuje filtr {private FilterConfig config; public void init (konfigurace FilterConfig) {this.config = config; } public FilterConfig getFilterConfig () {return config; } public void doFilter (požadavek ServletRequest, odpověď ServletResponse, řetězec FilterChain) hodí java.io.IOException, javax.servlet.ServletException {StringWrapper wrapper = nový StringWrapper((HttpServletResponse) response); řetěz.dofiltrovat(žádost, obal); Řetězec responseString = wrapper.toString(); Řetězec search = config.getInitParameter ("hledat"); String replace = config.getInitParameter ("nahradit"); if (search == null || replace == null) návrat; // Parametry nejsou správně nastaveny int index = responseString.indexOf (hledat); if (index! = -1) {String beforeReplace = responseString.substring (0, index); Řetězec afterReplace = responseString.substring (index + search.length ()); response.getWriter (). tisk(beforeReplace + replace + afterReplace); }} public void zničit () {config = null; }} 

Předchozí filtr vyhledá pojmenované parametry inicializace filtru Vyhledávání a nahradit; pokud jsou definovány, nahradí filtr první výskyt souboru Vyhledávání hodnota parametru s nahradit hodnota parametru.

SearchAndReplaceFilter.doFilter () zabalí (nebo ozdobí) objekt odezvy obalem (dekorátorem), který představuje odpověď. Když SearchAndReplaceFilter.doFilter () hovory chain.doFilter () k předání požadavku předá obálku namísto původní odpovědi. Požadavek je předán servletu, který generuje odpověď.

Když chain.doFilter () vrátí, servlet je hotový s požadavkem, tak jdu do práce. Nejprve zkontroluji Vyhledávání a nahradit parametry filtru; pokud je k dispozici, získám řetězec spojený s obálkou odpovědi, což je obsah odpovědi. Poté provedu náhradu a vytisknu ji zpět na odpověď.

Příklad 5 uvádí seznam StringWrapper třída.

Příklad 5. Malíř

importovat java.io. *; importovat javax.servlet. *; importovat javax.servlet.http. *; veřejná třída StringWrapper rozšiřuje HttpServletResponseWrapper {spisovatel StringWriter = nový StringWriter (); public StringWrapper (HttpServletResponse response) {super (odpověď); } public PrintWriter getWriter () {vrátit nový PrintWriter (zapisovač); } public String toString () {návrat Writer.toString (); }} 

StringWrapper, který zdobí odpověď HTTP v příkladu 4, je příponou HttpServletResponseWrapper, což nám ušetří práci s vytvářením základní třídy dekoratéra pro zdobení odpovědí HTTP. HttpServletResponseWrapper nakonec implementuje ServletResponse rozhraní, takže instance HttpServletResponseWrapper lze předat jakékoli metodě očekávající a ServletResponse objekt. Proto SearchAndReplaceFilter.doFilter () může volat chain.doFilter (požadavek, obal) namísto chain.doFilter (požadavek, Odezva).

Nyní, když máme filtr a obálku odpovědí, přidružme filtr k vzoru adresy URL a určíme vzory hledání a nahrazení: