Programmering

Følg kjeden av ansvar

Jeg byttet nylig til Mac OS X fra Windows, og jeg er begeistret for resultatene. Men igjen brukte jeg bare en kort femårs periode på Windows NT og XP; før det var jeg strengt tatt Unix-utvikler i 15 år, mest på Sun Microsystems-maskiner. Jeg var også heldig nok til å utvikle programvare under Nextstep, den frodige Unix-baserte forgjengeren til Mac OS X, så jeg er litt partisk.

Bortsett fra det vakre brukergrensesnittet Aqua, er Mac OS X Unix, uten tvil det beste operativsystemet som eksisterer. Unix har mange kule funksjoner; en av de mest kjente er rør, som lar deg lage kombinasjoner av kommandoer ved å føre en kommandos utgang til en annen. Anta for eksempel at du vil liste kildefiler fra Struts-kildedistribusjonen som påkaller eller definerer en metode som heter henrette(). Her er en måte å gjøre det på med et rør:

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

De grep kommando søker i filer etter regulære uttrykk; her bruker jeg den til å finne forekomster av strengen henrette( i filer som er funnet av finne kommando. grepproduksjonen er ledet inn i kjipt, som skriver ut det første token - avgrenset av et kolon - i hver linje av greputdata (en vertikal stang betyr et rør). At token er et filnavn, så jeg ender opp med en liste over filnavn som inneholder strengen henrette(.

Nå som jeg har en liste over filnavn, kan jeg bruke et annet rør til å sortere listen:

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

Denne gangen har jeg sendt listen over filnavn til sortere. Hva om du vil vite hvor mange filer som inneholder strengen henrette(? Det er enkelt med et annet rør:

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

De toalett kommando teller ord, linjer og byte. I dette tilfellet spesifiserte jeg -l muligheten til å telle linjer, en linje for hver fil. Jeg la også til en -u alternativ til sortere for å sikre unikhet for hvert filnavn ( -u alternativet filtrerer ut duplikater).

Rørene er kraftige fordi de lar deg dynamisk komponere en kjede av operasjoner. Programvaresystemer bruker ofte tilsvarende rør (f.eks. E-postfiltre eller et sett med filtre for en servlet). I hjertet av rør og filtre ligger et designmønster: Chain of Responsibility (CoR).

Merk: Du kan laste ned kildekoden til denne artikkelen fra Resources.

CoR introduksjon

Mønsteret Chain of Responsibility bruker en kjede av objekter til å håndtere en forespørsel, som vanligvis er en hendelse. Objekter i kjeden videresender forespørselen langs kjeden til et av objektene håndterer hendelsen. Behandlingen stopper etter at en hendelse er håndtert.

Figur 1 illustrerer hvordan CoR-mønsteret behandler forespørsler.

I Design mønstre, beskriver forfatterne kjeden av ansvarsmønster slik:

Unngå å koble avsenderen av en forespørsel til mottakeren ved å gi mer enn ett objekt en sjanse til å håndtere forespørselen. Kjed de mottatte gjenstandene og send forespørselen langs kjeden til en gjenstand håndterer den.

Ansvarsmønsteret gjelder dersom:

  • Du vil koble en forespørsels avsender og mottaker
  • Flere objekter, bestemt ved kjøretid, er kandidater til å håndtere en forespørsel
  • Du vil ikke spesifisere håndterere eksplisitt i koden din

Hvis du bruker CoR-mønsteret, husk:

  • Bare ett objekt i kjeden håndterer en forespørsel
  • Noen forespørsler blir kanskje ikke behandlet

Disse begrensningene er selvfølgelig for en klassisk CoR-implementering. I praksis er disse reglene bøyd; for eksempel er servletfiltre en CoR-implementering som gjør at flere filtre kan behandle en HTTP-forespørsel.

Figur 2 viser et CoR-mønster klassediagram.

Forespørselshåndterere er vanligvis utvidelser av en basisklasse som opprettholder en referanse til neste behandler i kjeden, kjent som etterfølger. Baseklassen kan implementere handleRequest () som dette:

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

Så som standard overfører håndterere forespørselen til neste behandler i kjeden. En konkret utvidelse av HandlerBase kan se slik ut:

 public class SpamFilter utvider HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Hvis meldingen er søppelpost // ta skrittrelatert handling. Ikke videresend meldingen. } annet {// Melding er ikke nettsøppel. super.handleRequest (mailMessage); // Send melding til neste filter i kjeden. }}} 

De SpamFilter håndterer forespørselen (antagelig mottak av ny e-post) hvis meldingen er spam, og derfor går forespørselen ikke lenger; ellers sendes pålitelige meldinger til neste behandler, antagelig et annet e-postfilter som ønsker å luke dem ut. Til slutt kan det siste filteret i kjeden lagre meldingen etter at den har passert mønster ved å gå gjennom flere filtre.

Legg merke til at de hypotetiske e-postfiltrene som er diskutert ovenfor, utelukker hverandre: I siste instans håndterer bare ett filter en forespørsel. Du kan velge å vende det ut og inn ved å la flere filtre håndtere en enkelt forespørsel, noe som er en bedre analogi med Unix-rør. Uansett er den underliggende motoren CoR-mønsteret.

I denne artikkelen diskuterer jeg to implementeringsmønstre for kjeden av ansvar: servletfiltre, en populær CoR-implementering som gjør det mulig for flere filtre å håndtere en forespørsel, og den originale Abstract Window Toolkit (AWT) hendelsesmodellen, en upopulær klassisk CoR-implementering som til slutt ble avviklet .

Servlet-filtre

I Java 2-plattformen, Enterprise Edition (J2EE) sine tidlige dager, ga noen servletcontainere en praktisk funksjon kjent som servlet chaining, hvor man i det vesentlige kunne bruke en liste over filtre til en servlet. Servlet-filtre er populære fordi de er nyttige for sikkerhet, komprimering, logging og mer. Og selvfølgelig kan du komponere en kjede av filtre for å gjøre noen eller alle disse tingene avhengig av kjøretidsforhold.

Med adventen av Java Servlet Specification versjon 2.3 ble filtre standardkomponenter. I motsetning til klassisk CoR tillater servletfiltre at flere objekter (filtre) i en kjede kan håndtere en forespørsel.

Servlet-filtre er et kraftig tillegg til J2EE. Også fra et designmønster synspunkt gir de en interessant vri: Hvis du vil endre forespørselen eller svaret, bruker du dekoratørmønsteret i tillegg til Regionsudvalget. Figur 3 viser hvordan servletfiltre fungerer.

Et enkelt servletfilter

Du må gjøre tre ting for å filtrere en servlet:

  • Implementere en servlet
  • Implementere et filter
  • Koble filteret og servleten

Eksempel 1-3 utfører alle tre trinnene etter hverandre:

Eksempel 1. En servlet

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

Eksempel 2. Et filter

importere java.io.PrintWriter; importere javax.servlet. *; importere javax.servlet.http.HttpServletRequest; offentlig klasse AuditFilter implementerer Filter {private ServletContext app = null; public void init (FilterConfig config) {app = config.getServletContext (); } offentlig tomrom doFilter(ServletRequest-forespørsel, ServletResponse-svar, FilterChain-kjede) kaster java.io.IOException, javax.servlet.ServletException {app.log ((((HttpServletRequest) forespørsel) .getServletPath ()); chain.doFilter(forespørsel, svar); } offentlig tomrom ødelegge () {}} 

Eksempel 3. Distribusjonsbeskrivelsen

    auditFilter AuditFilter <filterkartlegging>auditFilter/ filtrertServlet</ filter-mapping> filteredServlet FilteredServlet filteredServlet / filteredServlet ... 

Hvis du får tilgang til servletten med URL-en / filtrertServlet, den auditFilter får en sprekk på forespørsel før servleten. AuditFilter.doFilter skriver til loggfilen til servletcontaineren og ringer chain.doFilter () for å videresende forespørselen. Servlet-filtre er ikke påkrevd for å ringe chain.doFilter (); Hvis de ikke gjør det, blir forespørselen ikke videresendt. Jeg kan legge til flere filtre, som vil bli påkalt i den rekkefølgen de er erklært i den forrige XML-filen.

Nå som du har sett et enkelt filter, la oss se på et annet filter som endrer HTTP-responsen.

Filtrer svaret med dekoratormønsteret

I motsetning til det foregående filteret, må noen servletfiltre endre HTTP-forespørselen eller svaret. Interessant nok involverer denne oppgaven dekoratørmønsteret. Jeg diskuterte dekoratørmønsteret i to forrige Java Design Patterns artikler: "Overraske utviklervennene dine med designmønstre" og "Dekorer Java-koden."

Eksempel 4 viser et filter som utfører et enkelt søk og erstatter i svaret. Det filteret dekorerer servletsvaret og sender dekoratøren til servleten. Når servetten er ferdig med å skrive til det dekorerte svaret, utfører filteret et søk og erstatter det i svarets innhold.

Eksempel 4. Et søk og erstatt filter

importer java.io. *; importere javax.servlet. *; importere 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 request, ServletResponse response, FilterChain chain) kaster java.io.IOException, javax.servlet.ServletException {StringWrapper wrapper = ny StringWrapper((HttpServletResponse) svar); chain.doFilter(be om, innpakning); StrengresponsString = wrapper.toString(); Strengsøk = config.getInitParameter ("søk"); Streng erstatt = config.getInitParameter ("erstatt"); hvis (søk == null || erstatt == null) returner; // Parametere er ikke riktig angitt int index = responseString.indexOf (search); if (index! = -1) {String beforeRlace = responseString.substring (0, index); Streng etter erstat = responsString.substring (indeks + søk.lengde ()); respons.getWriter (). utskrift(før Bytt + erstatt + etter Bytt); }} offentlig tomrom ødelegge () {config = null; }} 

Det foregående filteret ser etter filterinit-parametere som heter Søk og erstatte; Hvis de er definert, erstatter filteret den første forekomsten av Søk parameterverdi med erstatte parameterverdi.

SearchAndReplaceFilter.doFilter () bryter (eller dekorerer) responsobjektet med en wrapper (dekoratør) som står for responsen. Når SearchAndReplaceFilter.doFilter () ringer chain.doFilter () for å videresende forespørselen, sender den innpakningen i stedet for det opprinnelige svaret. Forespørselen blir videresendt til servletten, som genererer svaret.

Når chain.doFilter () returnerer, servletten er ferdig med forespørselen, så jeg går på jobb. Først ser jeg etter Søk og erstatte filterparametere; hvis den er tilstede, får jeg strengen som er knyttet til responsinnpakningen, som er responsinnholdet. Så gjør jeg erstatningen og skriver den tilbake til svaret.

Eksempel 5 lister opp StringWrapper klasse.

Eksempel 5. En dekoratør

importer java.io. *; importere javax.servlet. *; importere javax.servlet.http. *; offentlig klasse StringWrapper utvider HttpServletResponseWrapper {StringWriter-forfatter = ny StringWriter (); offentlig StringWrapper (HttpServletResponse respons) {super (respons); } offentlig PrintWriter getWriter () {returner ny PrintWriter (skribent); } offentlig String toString () {return writer.toString (); }} 

StringWrapper, som dekorerer HTTP-responsen i eksempel 4, er en utvidelse av HttpServletResponseWrapper, som sparer oss for slitsomhet med å lage en dekoratørbaseklasse for dekorering av HTTP-svar. HttpServletResponseWrapper til slutt implementerer ServletResponse grensesnitt, så forekomster av HttpServletResponseWrapper kan overføres til hvilken som helst metode som forventer en ServletResponse gjenstand. Derfor SearchAndReplaceFilter.doFilter () kan ringe chain.doFilter (forespørsel, innpakning) i stedet for chain.doFilter (forespørsel, respons).

Nå som vi har et filter og en svaromslag, la oss knytte filteret til et URL-mønster og spesifisere søke- og erstatningsmønstre:

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