責任の連鎖に従う

最近、WindowsからMac OS Xに切り替えましたが、その結果にワクワクしています。しかし、繰り返しになりますが、私はWindowsNTとXPでわずか5年間しか過ごしませんでした。それ以前は、主にSun Microsystemsマシンで15年間、厳密にUnix開発者でした。私は幸運にも、Mac OS Xの前身であるUnixベースの緑豊かなNextstepでソフトウェアを開発できたので、少し偏見があります。

その美しいAquaユーザーインターフェイスを除けば、Mac OS XはUnixであり、間違いなく現存する最高のオペレーティングシステムです。Unixには多くの優れた機能があります。最もよく知られているのはパイプです。これを使用すると、あるコマンドの出力を別のコマンドの入力にパイプすることで、コマンドの組み合わせを作成できます。たとえば、という名前のメソッドを呼び出すか定義するStrutsソースディストリビューションのソースファイルを一覧表示するとしますexecute()。パイプを使用してこれを行う1つの方法は次のとおりです。

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

このgrepコマンドは、ファイルで正規表現を検索します。ここではexecute(findコマンドによって発掘されたファイル内の文字列の出現を見つけるためにそれを使用します。grepの出力はにパイプされますawk。これは、grep出力の各行にコロンで区切られた最初のトークンを出力します(垂直バーはパイプを示します)。そのトークンはファイル名なので、文字列を含むファイル名のリストになりますexecute(

ファイル名のリストができたので、別のパイプを使用してリストを並べ替えることができます。

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

今回は、ファイル名のリストをにパイプしましたsort。文字列を含むファイルの数を知りたい場合はどうなりますexecute(か?別のパイプで簡単です:

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

このwcコマンドは、ワード、行、およびバイトをカウントします。この場合、-lファイルごとに1行ずつ、行をカウントするオプションを指定しました。また、各ファイル名の一意性を確保するための-uオプションを追加しましたsort(この-uオプションは重複を除外します)。

パイプを使用すると、一連の操作を動的に構成できるため、強力です。ソフトウェアシステムは、多くの場合、パイプと同等のものを使用します(たとえば、電子メールフィルターまたはサーブレット用のフィルターのセット)。パイプとフィルターの中心には、Chain of Responsibility(CoR)というデザインパターンがあります。

注:この記事のソースコードは、リソースからダウンロードできます。

CoRの紹介

Chain of Responsibilityパターンは、オブジェクトのチェーンを使用して、通常はイベントである要求を処理します。チェーン内のオブジェクトは、オブジェクトの1つがイベントを処理するまで、チェーンに沿ってリクエストを転送します。イベントが処理された後、処理は停止します。

図1は、CoRパターンがリクエストを処理する方法を示しています。

デザインパターン、著者はこのようなChain of Responsibilityパターンについて説明します。

複数のオブジェクトにリクエストを処理する機会を与えることにより、リクエストの送信者をその受信者に結合することを避けてください。受信オブジェクトをチェーンし、オブジェクトがそれを処理するまでチェーンに沿ってリクエストを渡します。

Chain of Responsibilityパターンは、次の場合に適用できます。

  • リクエストの送信者と受信者を切り離したい
  • 実行時に決定される複数のオブジェクトは、リクエストを処理するための候補です
  • コードでハンドラーを明示的に指定したくない

CoRパターンを使用する場合は、次の点に注意してください。

  • チェーン内の1つのオブジェクトのみがリクエストを処理します
  • 一部のリクエストは処理されない場合があります

もちろん、これらの制限は、従来のCoR実装に対するものです。実際には、これらのルールは曲がっています。たとえば、サーブレットフィルタは、複数のフィルタがHTTPリクエストを処理できるようにするCoR実装です。

図2に、CoRパターンのクラス図を示します。

通常、リクエストハンドラは、チェーン内の次のハンドラへの参照を維持する基本クラスの拡張であり、と呼ばれますsuccessor。基本クラスは次のhandleRequest()ように実装できます。

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

したがって、デフォルトでは、ハンドラーは要求をチェーン内の次のハンドラーに渡します。の具体的な拡張は次のHandlerBaseようになります。

public class SpamFilter extends HandlerBase {public void handleRequest(SomeRequestObject mailMessage){if(isSpam(mailMessage)){//メッセージがスパムの場合//スパム関連のアクションを実行します。メッセージを転送しないでください。} else {//メッセージはスパムではありません。super.handleRequest(mailMessage); //メッセージをチェーン内の次のフィルターに渡します。}}}

SpamFilterハンドル要求(新しい電子メールの受信おそらく)メッセージがスパムであるため、要求はそれ以上行かない場合は、それ以外の場合、信頼できるメッセージは次のハンドラーに渡されます。おそらく、別の電子メールフィルターがそれらを取り除くことを目的としています。最終的に、チェーンの最後のフィルターは、いくつかのフィルターを通過することにより、メッセージがマスターを通過した後にメッセージを格納する可能性があります。

上記で説明した架空の電子メールフィルターは相互に排他的であることに注意してください。最終的には、1つのフィルターのみが要求を処理します。複数のフィルターで1つのリクエストを処理できるようにすることで、これを裏返しにすることもできます。これは、Unixパイプによく似ています。いずれにせよ、基盤となるエンジンはCoRパターンです。

この記事では、2つのChain of Responsibilityパターンの実装について説明します。サーブレットフィルターは、複数のフィルターが要求を処理できるようにする一般的なCoR実装であり、元のAbstract Window Toolkit(AWT)イベントモデルは、最終的に非推奨になった人気のない古典的なCoR実装です。 。

サーブレットフィルタ

Java 2 Platform、Enterprise Edition(J2EE)の初期の頃、一部のサーブレットコンテナは、サーブレットチェーンと呼ばれる便利な機能を提供していました。これにより、基本的にフィルタのリストをサーブレットに適用できました。サーブレットフィルタは、セキュリティ、圧縮、ロギングなどに役立つため、人気があります。そしてもちろん、実行時の条件に応じて、これらの一部またはすべてを実行するフィルターのチェーンを構成できます。

Javaサーブレット仕様バージョン2.3の登場により、フィルターは標準コンポーネントになりました。従来のCoRとは異なり、サーブレットフィルタを使用すると、チェーン内の複数のオブジェクト(フィルタ)でリクエストを処理できます。

サーブレットフィルタは、J2EEへの強力な追加機能です。また、デザインパターンの観点からは、興味深いひねりが加えられています。リクエストまたはレスポンスを変更する場合は、CoRに加えてデコレータパターンを使用します。図3は、サーブレットフィルタがどのように機能するかを示しています。

単純なサーブレットフィルタ

サーブレットをフィルタリングするには、次の3つのことを行う必要があります。

  • サーブレットを実装する
  • フィルタを実装する
  • フィルタとサーブレットを関連付けます

例1〜3は、3つのステップすべてを連続して実行します。

例1.サーブレット

インポートjava.io.PrintWriter; インポートjavax.servlet。*; インポート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( "フィルタリングされたサーブレットが呼び出されました"); }}

例2.フィルター

インポートjava.io.PrintWriter; インポートjavax.servlet。*; インポートjavax.servlet.http.HttpServletRequest; パブリッククラスAuditFilterはFilterを実装します{privateServletContext app = null; public void init(FilterConfig config){app = config.getServletContext(); } public void doFilter(ServletRequestリクエスト、ServletResponseレスポンス、FilterChainチェーン)はjava.io.IOException、javax.servlet.ServletException {app.log(((HttpServletRequest)request).getServletPath());をスローします。chain.doFilter(要求、応答); } public void destroy(){}}

例3.デプロイメント記述子

    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).

フィルタと応答ラッパーができたので、フィルタをURLパターンに関連付けて、検索パターンと置換パターンを指定しましょう。