Slijedite lanac odgovornosti

Nedavno sam prešao na Mac OS X s Windowsa i oduševljen sam rezultatima. Ali opet, samo sam kratko proveo pet godina na Windowsima NT i XP; prije toga bio sam strogo razvojni programer Unixa 15 godina, uglavnom na Sun Microsystems strojevima. Imao sam i sreće da sam razvio softver pod Nextstepom, bujnim Unixovim prethodnikom Mac OS X, pa sam pomalo pristran.

Osim lijepog korisničkog sučelja Aqua, Mac OS X je i Unix, vjerojatno najbolji operativni sustav koji postoji. Unix ima mnogo sjajnih karakteristika; jedna od najpoznatijih je cijev koja vam omogućuje stvaranje kombinacija naredbi cijevivši izlaz jedne naredbe na ulaz druge. Na primjer, pretpostavimo da želite navesti izvorne datoteke iz izvorne distribucije Struts koje pozivaju ili definiraju imenovanu metodu execute(). Evo jednog načina kako to učiniti pomoću cijevi:

grep "izvrši (" `pronađi $ STRUTS_SRC_DIR -ime" * .java "` | awk -F: '{ispis}'

grepNaredba traži datoteke za regularnim izrazima; ovdje ga koristim za pronalaženje pojava niza execute(u datotekama koje je findnaredba otkopala. grepIzlaz se cijevi u awkkoji se ispisuje prvi token - odvojen dvotačkom - u svaki redak grepizlaza (okomita traka označava cijev). Taj je token naziv datoteke, pa na kraju dobivam popis naziva datoteka koji sadrže niz execute(.

Sad kad imam popis imena datoteka, mogu upotrijebiti drugu cijev za sortiranje popisa:

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

Ovoga puta sastavio sam popis imena datoteka sort. Što ako želite znati koliko datoteka sadrži niz execute(? Jednostavno je s drugom cijevi:

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

wcNaredbu broji riječi, linije i bajtova. U ovom sam slučaju odredio -lopciju brojanja redaka, po jedan redak za svaku datoteku. Također sam dodao -uopciju kako sortbih osigurao jedinstvenost za svako ime datoteke ( -uopcija filtrira duplikate).

Cijevi su moćne jer vam omogućuju dinamično sastavljanje lanca operacija. Softverski sustavi često koriste ekvivalent cijevi (npr. Filtri za e-poštu ili skup filtara za servlet). U središtu cijevi i filtara leži obrazac dizajna: Lanac odgovornosti (CoR).

Napomena: Izvorni kôd ovog članka možete preuzeti iz izvora.

Uvod OR

Lanac odgovornosti koristi lanac objekata za obradu zahtjeva, što je obično događaj. Objekti u lancu prosljeđuju zahtjev duž lanca dok jedan od predmeta ne obradi događaj. Obrada se zaustavlja nakon obrade događaja.

Slika 1 ilustrira kako obrazac OR obrađuje zahtjeve.

U Obrascima dizajna , autori ovako opisuju obrazac Lanac odgovornosti:

Izbjegavajte spajanje pošiljatelja zahtjeva s njegovim primateljem davanjem više mogućnosti za obradu zahtjeva. Povežite objekte u lancu i prosljeđujte zahtjev duž lanca dok ga objekt ne obradi.

Uzorak lanca odgovornosti primjenjiv je ako:

  • Želite razdvojiti pošiljatelja i primatelja zahtjeva
  • Više objekata, određenih tijekom izvođenja, kandidati su za obradu zahtjeva
  • Ne želite eksplicitno navesti rukovatelje u svom kodu

Ako upotrebljavate uzorak OR-a, sjetite se:

  • Samo jedan objekt u lancu obrađuje zahtjev
  • Neki se zahtjevi možda neće obraditi

Ta su ograničenja, naravno, za klasičnu provedbu OR-a. U praksi se ta pravila savijaju; na primjer, servlet filtri su implementacija CoR-a koja omogućuje višestrukim filtrima da obrade HTTP zahtjev.

Slika 2 prikazuje dijagram klase uzoraka CoR.

Obično su obrađivači zahtjeva proširenja osnovne klase koja održava referencu na sljedećeg obrađivača u lancu, poznat kao successor. Osnovna klasa bi mogla implementirati handleRequest()ovako:

javna apstraktna klasa HandlerBase {... javna void handleRequest (SomeRequestObject sro) {if (nasljednik! = null) successor.handleRequest (sro); }}

Dakle, prema zadanim postavkama, obrađivači prosljeđuju zahtjev sljedećem obrađivaču u lancu. Konkretno proširenje HandlerBasemože izgledati ovako:

javna klasa SpamFilter proširuje HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Ako je poruka neželjena // poduzmite neželjenu radnju. Nemojte prosljeđivati ​​poruku. } else {// Poruka nije neželjena pošta. super.handleRequest (mailMessage); // Proslijedite poruku sljedećem filtru u lancu. }}}

U SpamFilterobrađuje zahtjev (vjerojatno primitak nove e) ako je poruka spam, i stoga, zahtjev ide dalje; u suprotnom, pouzdane poruke prosljeđuju se sljedećem obrađivaču, vjerojatno drugom filtru e-pošte koji ih želi ukloniti. Na kraju, posljednji filtar u lancu može pohraniti poruku nakon što prođe kroz prikupljanje pomicanjem kroz nekoliko filtara.

Imajte na umu da se prethodno navedeni hipotetski filtri e-pošte međusobno isključuju: U konačnici, samo jedan filtar obrađuje zahtjev. Možete se odlučiti okrenuti to iznutra puštajući da više filtara obrađuje jedan zahtjev, što je bolja analogija s Unix cijevima. U svakom slučaju, temeljni je motor obrazac CoR-a.

U ovom članku raspravljam o dvije implementacije obrasca Lanac odgovornosti: filtrima servleta, popularnoj implementaciji OR-a koja omogućuje obradu više filtara i zahtjevu, te izvornom modelu događaja Abstract Window Toolkit (AWT), nepopularnoj klasičnoj implementaciji OR-a koja je u konačnici zastarjela .

Servlet filtri

U ranim danima Java 2 Platform, Enterprise Edition (J2EE), neki spremnici servleta pružali su korisnu značajku poznatu kao lanac servleta, pri čemu se u osnovi može primijeniti popis filtara na servlet. Servlet filtri su popularni jer su korisni za sigurnost, kompresiju, bilježenje i još mnogo toga. I, naravno, možete sastaviti lanac filtara za obavljanje nekih ili svih tih stvari, ovisno o uvjetima izvođenja.

Pojavom Java Servlet Specification verzije 2.3, filtri su postali standardne komponente. Za razliku od klasičnog CoR-a, filtri servleta omogućuju više objekata (filtara) u lancu da obrađuju zahtjev.

Servlet filtri moćan su dodatak J2EE. Također, sa stajališta dizajnerskih uzoraka, pružaju zanimljiv zaokret: ako želite izmijeniti zahtjev ili odgovor, uz CoR koristite i uzorak dekoratora. Slika 3 prikazuje kako funkcioniraju filtri servleta.

Jednostavan filtar servleta

Da biste filtrirali servlet, morate učiniti tri stvari:

  • Provedite servlet
  • Primijenite filtar
  • Pridružite filtar i servlet

Primjeri 1-3 izvode sva tri koraka uzastopno:

Primjer 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("Filtered Servlet invoked"); } } 

Example 2. A filter

import java.io.PrintWriter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; public class AuditFilter implements Filter { private ServletContext app = null; public void init(FilterConfig config) { app = config.getServletContext(); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, javax.servlet.ServletException { app.log(((HttpServletRequest)request).getServletPath()); chain.doFilter(request, response); } public void destroy() { } } 

Example 3. The deployment descriptor

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

If you access the servlet with the URL /filteredServlet, the auditFilter gets a crack at the request before the servlet. AuditFilter.doFilter writes to the servlet container log file and calls chain.doFilter() to forward the request. Servlet filters are not required to call chain.doFilter(); if they don't, the request is not forwarded. I can add more filters, which would be invoked in the order they are declared in the preceding XML file.

Now that you've seen a simple filter, let's look at another filter that modifies the HTTP response.

Filter the response with the Decorator pattern

Unlike the preceding filter, some servlet filters need to modify the HTTP request or response. Interestingly enough, that task involves the Decorator pattern. I discussed the Decorator pattern in two previous Java Design Patterns articles: "Amaze Your Developer Friends with Design Patterns" and "Decorate Your Java Code."

Example 4 lists a filter that performs a simple search and replace in the body of the response. That filter decorates the servlet response and passes the decorator to the servlet. When the servlet finishes writing to the decorated response, the filter performs a search and replace within the response's content.

Example 4. A search and replace filter

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SearchAndReplaceFilter implements Filter { private FilterConfig config; public void init(FilterConfig config) { this.config = config; } public FilterConfig getFilterConfig() { return config; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, javax.servlet.ServletException { StringWrapper wrapper = new StringWrapper((HttpServletResponse)response); chain.doFilter(request, wrapper); String responseString = wrapper.toString(); String search = config.getInitParameter("search"); String replace = config.getInitParameter("replace"); if(search == null || replace == null) return; // Parameters not set properly int index = responseString.indexOf(search); if(index != -1) { String beforeReplace = responseString.substring(0, index); String afterReplace=responseString.substring(index + search.length()); response.getWriter().print(beforeReplace + replace + afterReplace); } } public void destroy() { config = null; } } 

The preceding filter looks for filter init parameters named search and replace; if they are defined, the filter replaces the first occurrence of the search parameter value with the replace parameter value.

SearchAndReplaceFilter.doFilter() wraps (or decorates) the response object with a wrapper (decorator) that stands in for the response. When SearchAndReplaceFilter.doFilter() calls chain.doFilter() to forward the request, it passes the wrapper instead of the original response. The request is forwarded to the servlet, which generates the response.

When chain.doFilter() returns, the servlet is done with the request, so I go to work. First, I check for the search and replace filter parameters; if present, I obtain the string associated with the response wrapper, which is the response content. Then I make the substitution and print it back to the response.

Example 5 lists the StringWrapper class.

Example 5. A decorator

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class StringWrapper extends HttpServletResponseWrapper { StringWriter writer = new StringWriter(); public StringWrapper(HttpServletResponse response) { super(response); } public PrintWriter getWriter() { return new PrintWriter(writer); } public String toString() { return writer.toString(); } } 

StringWrapper, which decorates the HTTP response in Example 4, is an extension of HttpServletResponseWrapper, which spares us the drudgery of creating a decorator base class for decorating HTTP responses. HttpServletResponseWrapper ultimately implements the ServletResponse interface, so instances of HttpServletResponseWrapper can be passed to any method expecting a ServletResponse object. That's why SearchAndReplaceFilter.doFilter() can call chain.doFilter(request, wrapper) instead of chain.doFilter(request, response).

Sad kad imamo filtar i omot odgovora, pridružimo filtar uzorku URL-a i odredimo uzorke pretraživanja i zamjene: