Folgen Sie der Kette der Verantwortung

Ich bin kürzlich von Windows zu Mac OS X gewechselt und bin von den Ergebnissen begeistert. Andererseits habe ich nur kurze fünf Jahre mit Windows NT und XP verbracht. Davor war ich 15 Jahre lang ausschließlich Unix-Entwickler, hauptsächlich auf Sun Microsystems-Computern. Ich hatte auch das Glück, Software unter Nextstep zu entwickeln, dem üppigen Unix-basierten Vorgänger von Mac OS X, also bin ich ein wenig voreingenommen.

Abgesehen von seiner schönen Aqua-Benutzeroberfläche ist Mac OS X Unix, das wohl beste Betriebssystem, das es gibt. Unix hat viele coole Funktionen; Eine der bekanntesten ist die Pipe , mit der Sie Befehlskombinationen erstellen können, indem Sie die Ausgabe eines Befehls an die Eingabe eines anderen weiterleiten. Angenommen, Sie möchten Quelldateien aus der Struts-Quelldistribution auflisten, die eine Methode mit dem Namen aufrufen oder definieren execute(). Hier ist eine Möglichkeit, dies mit einer Pfeife zu tun:

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

Der grepBefehl durchsucht Dateien nach regulären Ausdrücken. Hier verwende ich es, um Vorkommen der Zeichenfolge execute(in Dateien zu finden, die vom findBefehl ausgegraben wurden . grepDie Ausgabe wird in weitergeleitet awk, wodurch das erste durch einen Doppelpunkt getrennte Token in jeder Zeile der grepAusgabe ausgegeben wird (ein vertikaler Balken kennzeichnet eine Pipe). Dieses Token ist ein Dateiname, daher erhalte ich eine Liste von Dateinamen, die die Zeichenfolge enthalten execute(.

Nachdem ich eine Liste mit Dateinamen habe, kann ich die Liste mit einer anderen Pipe sortieren:

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

Dieses Mal habe ich die Liste der Dateinamen an weitergeleitet sort. Was ist, wenn Sie wissen möchten, wie viele Dateien die Zeichenfolge enthalten execute(? Mit einer anderen Pfeife ist es einfach:

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

Der wcBefehl zählt Wörter, Zeilen und Bytes. In diesem Fall habe ich die -lOption zum Zählen von Zeilen angegeben, eine Zeile für jede Datei. Ich habe auch eine -uOption hinzugefügt sort, um die Eindeutigkeit für jeden Dateinamen sicherzustellen (die -uOption filtert Duplikate heraus).

Pipes sind leistungsstark, da Sie damit dynamisch eine Operationskette erstellen können. Softwaresysteme verwenden häufig das Äquivalent von Pipes (z. B. E-Mail-Filter oder eine Reihe von Filtern für ein Servlet). Das Herzstück von Rohren und Filtern ist ein Entwurfsmuster: Chain of Responsibility (AdR).

Hinweis: Sie können den Quellcode dieses Artikels von Resources herunterladen.

Einführung des AdR

Das Muster der Verantwortungskette verwendet eine Kette von Objekten, um eine Anforderung zu verarbeiten, bei der es sich normalerweise um ein Ereignis handelt. Objekte in der Kette leiten die Anforderung entlang der Kette weiter, bis eines der Objekte das Ereignis behandelt. Die Verarbeitung wird beendet, nachdem ein Ereignis behandelt wurde.

Abbildung 1 zeigt, wie das AdR-Muster Anforderungen verarbeitet.

In Design Patterns beschreiben die Autoren das Muster der Verantwortungskette folgendermaßen:

Vermeiden Sie es, den Absender einer Anfrage an den Empfänger zu koppeln, indem Sie mehr als einem Objekt die Möglichkeit geben, die Anfrage zu bearbeiten. Verketten Sie die empfangenden Objekte und leiten Sie die Anforderung entlang der Kette weiter, bis ein Objekt sie verarbeitet.

Das Muster der Verantwortungskette gilt, wenn:

  • Sie möchten den Absender und den Empfänger einer Anfrage entkoppeln
  • Mehrere zur Laufzeit bestimmte Objekte sind Kandidaten für die Bearbeitung einer Anfrage
  • Sie möchten Handler nicht explizit in Ihrem Code angeben

Wenn Sie das AdR-Muster verwenden, denken Sie daran:

  • Nur ein Objekt in der Kette verarbeitet eine Anforderung
  • Einige Anfragen werden möglicherweise nicht bearbeitet

Diese Einschränkungen gelten natürlich für eine klassische AdR-Implementierung. In der Praxis sind diese Regeln verbogen; Servlet-Filter sind beispielsweise eine AdR-Implementierung, mit der mehrere Filter eine HTTP-Anforderung verarbeiten können.

Abbildung 2 zeigt ein AdR-Musterklassendiagramm.

In der Regel sind Anforderungshandler Erweiterungen einer Basisklasse, die einen Verweis auf den nächsten Handler in der Kette enthält, der als successor. Die Basisklasse könnte Folgendes implementieren handleRequest():

öffentliche abstrakte Klasse HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (Nachfolger! = null) successor.handleRequest (sro); }}

Standardmäßig übergeben Handler die Anforderung an den nächsten Handler in der Kette. Eine konkrete Erweiterung von HandlerBasekönnte folgendermaßen aussehen:

öffentliche Klasse SpamFilter erweitert HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Wenn die Nachricht Spam ist // Spam-bezogene Aktion ausführen. Nachricht nicht weiterleiten. } else {// Nachricht ist kein Spam. super.handleRequest (mailMessage); // Nachricht an nächsten Filter in der Kette übergeben. }}}

Der SpamFilterbearbeitet die Anfrage (vermutlich den Empfang einer neuen E-Mail), wenn es sich bei der Nachricht um Spam handelt, und daher geht die Anfrage nicht weiter. Andernfalls werden vertrauenswürdige Nachrichten an den nächsten Handler weitergeleitet, vermutlich an einen anderen E-Mail-Filter, der versucht, sie auszusortieren. Möglicherweise speichert der letzte Filter in der Kette die Nachricht, nachdem er die Musterung bestanden hat, indem er sich durch mehrere Filter bewegt.

Beachten Sie, dass sich die oben beschriebenen hypothetischen E-Mail-Filter gegenseitig ausschließen: Letztendlich verarbeitet nur ein Filter eine Anforderung. Sie können dies auf den Kopf stellen, indem Sie mehrere Filter eine einzelne Anforderung bearbeiten lassen. Dies ist eine bessere Analogie zu Unix-Pipes. In jedem Fall ist der zugrunde liegende Motor das AdR-Muster.

In diesem Artikel werde ich zwei Implementierungen von Chain of Responsibility-Mustern erläutern: Servlet-Filter, eine beliebte CoR-Implementierung, mit der mehrere Filter eine Anforderung bearbeiten können, und das ursprüngliche AWT-Ereignismodell (Abstract Window Toolkit), eine unpopuläre klassische CoR-Implementierung, die letztendlich veraltet war .

Servlet-Filter

In den Anfängen der Java 2 Platform, Enterprise Edition (J2EE), stellten einige Servlet-Container eine praktische Funktion bereit, die als Servlet-Verkettung bezeichnet wird, wobei man im Wesentlichen eine Liste von Filtern auf ein Servlet anwenden kann. Servlet-Filter sind beliebt, da sie für Sicherheit, Komprimierung, Protokollierung und mehr nützlich sind. Und natürlich können Sie eine Reihe von Filtern zusammenstellen, um einige oder alle dieser Aufgaben abhängig von den Laufzeitbedingungen auszuführen.

Mit dem Aufkommen der Java Servlet Specification Version 2.3 wurden Filter zu Standardkomponenten. Im Gegensatz zum klassischen AdR ermöglichen Servlet-Filter mehreren Objekten (Filtern) in einer Kette, eine Anfrage zu bearbeiten.

Servlet-Filter sind eine leistungsstarke Ergänzung zu J2EE. Unter dem Gesichtspunkt der Entwurfsmuster bieten sie auch eine interessante Wendung: Wenn Sie die Anforderung oder die Antwort ändern möchten, verwenden Sie zusätzlich zum AdR das Dekorationsmuster. Abbildung 3 zeigt, wie Servlet-Filter funktionieren.

Ein einfacher Servlet-Filter

Sie müssen drei Dinge tun, um ein Servlet zu filtern:

  • Implementieren Sie ein Servlet
  • Implementieren Sie einen Filter
  • Ordnen Sie den Filter und das Servlet zu

Die Beispiele 1-3 führen alle drei Schritte nacheinander aus:

Beispiel 1. Ein 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).

Nachdem wir nun einen Filter und einen Antwort-Wrapper haben, verknüpfen wir den Filter mit einem URL-Muster und geben Such- und Ersetzungsmuster an: