Programmering

Følg kæden for ansvarlighed

Jeg skiftede for nylig til Mac OS X fra Windows, og jeg er begejstret for resultaterne. Men igen brugte jeg kun en kort femårs periode på Windows NT og XP; før det var jeg strengt taget Unix-udvikler i 15 år, hovedsagelig på Sun Microsystems-maskiner. Jeg var også heldig nok til at udvikle software under Nextstep, den frodige Unix-baserede forgænger til Mac OS X, så jeg er lidt partisk.

Bortset fra den smukke Aqua-brugergrænseflade er Mac OS X Unix, uden tvivl det bedste operativsystem, der findes. Unix har mange seje funktioner; en af ​​de mest kendte er rør, som lader dig oprette kombinationer af kommandoer ved at føre en kommandos output til en andres input. Antag for eksempel, at du vil liste kildefiler fra Struts-kildedistributionen, der påberåber eller definerer en navngivet metode udføre (). Her er en måde at gøre det på med et rør:

 grep "execute (" `find $ STRUTS_SRC_DIR-navn" * .java "` | awk -F: '{print}' 

Det grep kommando søger i filer efter regulære udtryk; her bruger jeg det til at finde forekomster af strengen udføre ( i filer udgravet af finde kommando. grepoutput sendes i rør akavet, der udskriver det første token - afgrænset af et kolon - i hver linje af grepoutput (en lodret bjælke betyder et rør). Det token er et filnavn, så jeg ender med en liste over filnavne, der indeholder strengen udføre (.

Nu hvor jeg har en liste med filnavne, kan jeg bruge et andet rør til at sortere listen:

 grep "execute (" `find $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}' | sortere

Denne gang har jeg sendt listen over filnavne til sortere. Hvad hvis du vil vide, hvor mange filer der indeholder strengen udføre (? Det er let med et andet rør:

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

Det Toilet kommando tæller ord, linjer og bytes. I dette tilfælde specificerede jeg -l mulighed for at tælle linjer, en linje for hver fil. Jeg tilføjede også en -u mulighed for at sortere for at sikre entydighed for hvert filnavn ( -u option filtrerer duplikater ud).

Rør er kraftfulde, fordi de giver dig mulighed for dynamisk at komponere en kæde af operationer. Softwaresystemer anvender ofte det tilsvarende rør (f.eks. E-mail-filtre eller et sæt filtre til en servlet). Kernen i rør og filtre ligger et designmønster: Chain of Responsibility (CoR).

Bemærk: Du kan downloade denne artikels kildekode fra Resources.

Regionsudvalgets introduktion

Mønsteret Chain of Responsibility bruger en kæde af objekter til at håndtere en anmodning, hvilket typisk er en begivenhed. Objekter i kæden videresender anmodningen langs kæden, indtil et af objekterne håndterer begivenheden. Behandlingen stopper, når en begivenhed er håndteret.

Figur 1 illustrerer, hvordan CoR-mønsteret behandler anmodninger.

I Designmønstre, beskriver forfatterne Chain of Responsibility mønster som dette:

Undgå at koble afsenderen af ​​en anmodning til modtageren ved at give mere end et objekt en chance for at håndtere anmodningen. Kæd de modtagende genstande, og send anmodningen langs kæden, indtil en genstand håndterer den.

Mønsteret om ansvarskæde gælder, hvis:

  • Du vil afkoble en anmodnings afsender og modtager
  • Flere objekter, bestemt ved kørsel, er kandidater til at håndtere en anmodning
  • Du ønsker ikke at angive håndterere eksplicit i din kode

Hvis du bruger CoR-mønsteret, skal du huske:

  • Kun et objekt i kæden håndterer en anmodning
  • Nogle anmodninger bliver muligvis ikke håndteret

Disse begrænsninger gælder selvfølgelig for en klassisk RU-implementering. I praksis er disse regler bøjet; for eksempel er servletfiltre en CoR-implementering, der gør det muligt for flere filtre at behandle en HTTP-anmodning.

Figur 2 viser et CoR-mønster klassediagram.

Anmodningshåndterere er typisk udvidelser af en basisklasse, der opretholder en henvisning til den næste handler i kæden, kendt som efterfølger. Baseklassen kan implementere handleRequest () sådan her:

 offentlig abstrakt klasse HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (successor! = null) successor.handleRequest (sro); }} 

Så som standard sender håndterere anmodningen til den næste handler i kæden. En konkret udvidelse af HandlerBase kan se sådan ud:

 public class SpamFilter udvider HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Hvis meddelelsen er spam // foretag spam-relateret handling. Send ikke besked. } andet {// Besked er ikke spam. super.handleRequest (mailMessage); // Send besked til næste filter i kæden. }}} 

Det SpamFilter håndterer anmodningen (formodentlig modtagelse af ny e-mail), hvis beskeden er spam, og derfor går anmodningen ikke længere; Ellers sendes pålidelige meddelelser til den næste behandler, formodentlig et andet e-mail-filter, der søger at udrydde dem. Til sidst kan det sidste filter i kæden muligvis gemme meddelelsen, efter at den har passeret mønster ved at bevæge sig gennem flere filtre.

Bemærk, at de hypotetiske e-mail-filtre, der er diskuteret ovenfor, udelukker hinanden: I sidste ende håndterer kun et filter en anmodning. Du kan vælge at vende det udad ved at lade flere filtre håndtere en enkelt anmodning, hvilket er en bedre analogi med Unix-rør. Uanset hvad er den underliggende motor CoR-mønster.

I denne artikel diskuterer jeg to Chain of Responsibility-mønsterimplementeringer: servletfiltre, en populær CoR-implementering, der gør det muligt for flere filtre at håndtere en anmodning, og den originale Abstract Window Toolkit (AWT) begivenhedsmodel, en upopulær klassisk CoR-implementering, der i sidste ende blev udfaset .

Servlet filtre

I Java 2-platformen, Enterprise Edition (J2EE) 's tidlige dage, leverede nogle servletcontainere en praktisk funktion kendt som servlet chaining, hvor man i det væsentlige kunne anvende en liste over filtre til en servlet. Servlet-filtre er populære, fordi de er nyttige til sikkerhed, komprimering, logning og mere. Og selvfølgelig kan du komponere en kæde af filtre til at gøre nogle eller alle disse ting afhængigt af runtime-forhold.

Med fremkomsten af ​​Java Servlet Specification version 2.3 blev filtre standardkomponenter. I modsætning til klassisk CoR tillader servletfiltre flere objekter (filtre) i en kæde til at håndtere en anmodning.

Servlet-filtre er en kraftig tilføjelse til J2EE. Fra et designmønster synspunkt giver de også et interessant twist: Hvis du vil ændre anmodningen eller svaret, bruger du dekoratørmønsteret ud over Regionsudvalget. Figur 3 viser, hvordan servletfiltre fungerer.

Et simpelt servletfilter

Du skal gøre tre ting for at filtrere en servlet:

  • Implementere en servlet
  • Implementere et filter
  • Tilknyt filteret og servlet

Eksempel 1-3 udfører alle tre trin i rækkefølge:

Eksempel 1. En servlet

import java.io.PrintWriter; import javax.servlet. *; import javax.servlet.http. *; offentlig klasse FilteredServlet udvider HttpServlet {public void doGet (HttpServletRequest anmodning, HttpServletResponse svar) kaster ServletException, java.io.IOException {PrintWriter out = respons.getWriter (); out.println ("Filtered Servlet invoked"); }} 

Eksempel 2. Et filter

import java.io.PrintWriter; import javax.servlet. *; import javax.servlet.http.HttpServletRequest; offentlig klasse AuditFilter implementerer Filter {private ServletContext app = null; offentlig ugyldig init (FilterConfig config) {app = config.getServletContext (); } offentlig ugyldighed doFilter(ServletRequest-anmodning, ServletResponse-svar, FilterChain-kæde) kaster java.io.IOException, javax.servlet.ServletException {app.log ((((HttpServletRequest) anmodning) .getServletPath ()); chain.doFilter(anmodning, svar); } offentlig tomrum ødelægge () {}} 

Eksempel 3. Implementeringsbeskrivelsen

    auditFilter AuditFilter <filter-kortlægning>auditFilter/ filtreretServlet</ filter-kortlægning> filtreretServlet FiltreretServlet filtreretServlet / filtreretServlet ... 

Hvis du åbner servlet med URL'en / filtreretServlet, det auditFilter får en revne efter anmodning før servlet. AuditFilter.doFilter skriver til logfilen til servletcontaineren og kalder chain.doFilter () at videresende anmodningen. Servlet-filtre er ikke påkrævet for at ringe chain.doFilter (); hvis de ikke gør det, videresendes anmodningen ikke. Jeg kan tilføje flere filtre, som vil blive påberåbt i den rækkefølge, de er erklæret i den foregående XML-fil.

Nu hvor du har set et simpelt filter, lad os se på et andet filter, der ændrer HTTP-svaret.

Filtrer svaret med dekoratormønsteret

I modsætning til det foregående filter skal nogle servletfiltre ændre HTTP-anmodningen eller svaret. Interessant nok involverer denne opgave dekoratørmønsteret. Jeg diskuterede dekoratørmønsteret i to foregående Java designmønstre artikler: "Overrask dine udviklervenner med designmønstre" og "Dekorer din Java-kode."

Eksempel 4 viser et filter, der udfører en simpel søgning og erstatning i reaktionens hoveddel. Dette filter dekorerer servletsvaret og sender dekoratøren til servleten. Når servlet er færdig med at skrive til det dekorerede svar, udfører filteret en søgning og udskiftning inden for svarets indhold.

Eksempel 4. Et søg og udskift filter

import java.io. *; import javax.servlet. *; import javax.servlet.http. *; offentlig klasse SearchAndReplaceFilter implementerer Filter {private FilterConfig config; public void init (FilterConfig config) {this.config = config; } offentlig FilterConfig getFilterConfig () {return config; } public void doFilter (ServletRequest anmodning, ServletResponse svar, FilterChain kæde) kaster java.io.IOException, javax.servlet.ServletException {StringWrapper wrapper = ny StringWrapper((HttpServletResponse) svar); chain.doFilter(anmodning, indpakning); StrengresponsString = wrapper.toString(); Strengsøgning = config.getInitParameter ("søg"); Streng erstatte = config.getInitParameter ("udskift"); hvis (søg == null || erstat == null) returnerer; // Parametre er ikke indstillet korrekt int index = responseString.indexOf (search); if (index! = -1) {String beforeRlace = responseString.substring (0, index); Streng efterudskiftning = responsString.substring (indeks + søg.længde ()); respons.getWriter (). udskriv(før Udskift + udskift + efter Udskift); }} offentlig tomrum ødelægge () {config = null; }} 

Det foregående filter ser efter navngivne filterinit-parametre Søg og erstatte; hvis de er defineret, erstatter filteret den første forekomst af Søg parameterværdi med erstatte parameterværdi.

SearchAndReplaceFilter.doFilter () indpakker (eller dekorerer) responsobjektet med en indpakning (dekoratør), der står for svaret. Hvornår SearchAndReplaceFilter.doFilter () opkald chain.doFilter () for at videresende anmodningen sender den indpakningen i stedet for det originale svar. Anmodningen videresendes til servleten, som genererer svaret.

Hvornår chain.doFilter () vender tilbage, servlet er færdig med anmodningen, så jeg går på arbejde. Først tjekker jeg efter Søg og erstatte filterparametre; hvis den er til stede, får jeg den streng, der er knyttet til responsindpakningen, hvilket er responsindholdet. Så foretager jeg udskiftningen og udskriver den tilbage til svaret.

Eksempel 5 viser listen StringWrapper klasse.

Eksempel 5. En dekoratør

import java.io. *; import javax.servlet. *; import javax.servlet.http. *; offentlig klasse StringWrapper udvider HttpServletResponseWrapper {StringWriter forfatter = ny StringWriter (); offentlig StringWrapper (HttpServletResponse svar) {super (svar); } offentlig PrintWriter getWriter () {returner ny PrintWriter (forfatter); } offentlig String toString () {return writer.toString (); }} 

StringWrapper, som dekorerer HTTP-svaret i eksempel 4, er en udvidelse af HttpServletResponseWrapper, som sparer os for trængsel ved at skabe en dekoratørbaseklasse til dekoration af HTTP-svar. HttpServletResponseWrapper i sidste ende implementerer ServletResponse interface, så forekomster af HttpServletResponseWrapper kan overføres til enhver metode, der forventer en ServletResponse objekt. Derfor SearchAndReplaceFilter.doFilter () kan ringe chain.doFilter (anmodning, indpakning) i stedet for chain.doFilter (anmodning, respons).

Nu hvor vi har et filter og et svarindpakning, lad os forbinde filteret med et URL-mønster og angive søge- og erstatningsmønstre: