デザイン パターンの美しさ 63 - 責任パターンの連鎖 (パート 2): フレームワークで一般的に使用されるフィルターとインターセプターはどのように実装されていますか?

63 | 責任連鎖モード (パート 2): フレームワークで一般的に使用されるフィルターとインターセプターはどのように実装されていますか?

前回のクラスでは、一連の責任モデルの原則と実装について学び、センシティブ ワード フィルタリング フレームワークの例を通じて、一連の責任モデルの設計意図を示しました。本質的には、ほとんどの設計パターンと同様に、コードを分離し、コードの複雑さに対処し、コードがオープンとクローズの原則を満たすようにし、コードのスケーラビリティを向上させることです。

さらに、責任連鎖モデルは、フレームワークの開発でフレームワークの拡張ポイントを提供するためによく使用され、フレームワークのユーザーがフレームワークのソース コードを変更することなく、拡張ポイントに基づいて新しい機能を追加できるようにすることにも言及しました。実際、より具体的には、チェーン オブ レスポンシビリティ パターンは、フレームワーク フィルターとインターセプターの開発に最もよく使用されます。今日は、Java 開発で一般的に使用される 2 つのコンポーネントである Servlet Filter と Spring Interceptor を使用して、フレームワーク開発におけるそのアプリケーションについて説明します。

早速、今日から本格的に勉強を始めましょう!

サーブレット フィルタ

サーブレット フィルタは、Java サーブレット仕様で定義されたコンポーネントです。フィルタとして中国語に翻訳できます。認証、電流制限、ロギング、検証パラメータなどの HTTP リクエストをフィルタリングできます。サーブレット仕様の一部であるため、サーブレットをサポートする Web コンテナー (Tomcat、Jetty など) であれば、フィルター機能をサポートします。理解を助けるために、以下に示すように、それがどのように機能するかを説明する図を描きました。

ここに画像の説明を挿入

実際のプロジェクトでは、Servlet Filter をどのように使用するのでしょうか? 以下のような簡単なサンプルコードを書きました。フィルターを追加するには、javax.servlet.Filter インターフェースを実装するフィルター クラスを定義し、それを web.xml 構成ファイルで構成するだけです。Web コンテナーが開始すると、web.xml の構成が読み取られ、フィルター オブジェクトが作成されます。リクエストが来ると、サーブレットによって処理される前にフィルターを通過します。

public class LogFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 在创建Filter时自动调用,
    // 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("拦截客户端发送来的请求.");
    chain.doFilter(request, response);
    System.out.println("拦截发送给客户端的响应.");
  }

  @Override
  public void destroy() {
    // 在销毁Filter时自动调用
  }
}

// 在web.xml配置文件中如下配置:
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

先ほどのサンプル コードから、コードを変更せずに、javax. では、Servlet Filter はどのようにしてこのような優れたスケーラビリティを実現しているのでしょうか? Chain of Responsibility パターンを使用していると推測したはずです。それでは、ソースコードを分析して、底辺の実装方法を詳しく見てみましょう。

前のレッスンで、Chain of Responsibility パターンの実装には、ハンドラー インターフェイス (IHandler) または抽象クラス (Handler)、およびハンドラー チェーン (HandlerChain) が含まれることを説明しました。Servlet Filter に対応し、javax.servlet.Filter はプロセッサ インターフェイス、FilterChain はプロセッサ チェーンです。次に、FilterChain の実装方法に注目しましょう。

ただし、前述したように、Servlet は単なる仕様であり、特定の実装は含まれていません. したがって、Servlet 内の FilterChain は単なるインターフェイス定義です。具体的な実装クラスは、Servlet 仕様に準拠した Web コンテナによって提供されます.たとえば、ApplicationFilterChain クラスは、Tomcat が提供する FilterChain の実装クラスです.ソース コードは次のとおりです.

コードを理解しやすくするために、コードを単純化し、設計アイデアに関連するコードの断片のみを残しました。Tomcat で完全なコードを自分で確認できます。

public final class ApplicationFilterChain implements FilterChain {
  private int pos = 0; //当前执行到了哪个filter
  private int n; //filter的个数
  private ApplicationFilterConfig[] filters;
  private Servlet servlet;

  @Override
  public void doFilter(ServletRequest request, ServletResponse response) {
    if (pos < n) {
      ApplicationFilterConfig filterConfig = filters[pos++];
      Filter filter = filterConfig.getFilter();
      filter.doFilter(request, response, this);
    } else {
      // filter都处理完毕后,执行servlet
      servlet.service(request, response);
    }
  }

  public void addFilter(ApplicationFilterConfig filterConfig) {
    for (ApplicationFilterConfig filter:filters)
      if (filter==filterConfig)
         return;

    if (n == filters.length) {//扩容
      ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];
      System.arraycopy(filters, 0, newFilters, 0, n);
      filters = newFilters;
    }
    filters[n++] = filterConfig;
  }
}

ApplicationFilterChain の doFilter() 関数のコード実装は非常に複雑で、実際には再帰呼び出しです。各 Filter (LogFilter など) の doFilter() コードを使用して、ApplicationFilterChain の 12 行目のコードを直接置き換えることができ、再帰呼び出しであることが一目でわかります。下図のように交換しました。

  @Override
  public void doFilter(ServletRequest request, ServletResponse response) {
    if (pos < n) {
      ApplicationFilterConfig filterConfig = filters[pos++];
      Filter filter = filterConfig.getFilter();
      //filter.doFilter(request, response, this);
      //把filter.doFilter的代码实现展开替换到这里
      System.out.println("拦截客户端发送来的请求.");
      chain.doFilter(request, response); // chain就是this
      System.out.println("拦截发送给客户端的响应.")
    } else {
      // filter都处理完毕后,执行servlet
      servlet.service(request, response);
    }
  }

この実装は主に doFilter() メソッドで双方向のインターセプトをサポートするためのものであり、クライアントから送信されたリクエストをインターセプトするだけでなく、クライアントに送信されたレスポンスもインターセプトできます. LogFilter の例を組み合わせて、後で比較することができます. . Spring Interceptor については、自分で理解してください。ただし、前回のクラスで示した 2 つの実装方法では、ビジネス ロジックの実行前後に処理コードを追加することはできません。

スプリングインターセプター

サーブレット フィルターについて話しましたが、機能が非常に似ているものについて話しましょう。中国語に翻訳された Spring Interceptor はインターセプターです。英単語と中国語の翻訳は異なりますが、基本的にはこの 2 つは概念と見なすことができ、どちらも HTTP リクエストを傍受するために使用されます。

これらの違いは、Servlet Filter が Servlet 仕様の一部であり、その実装が Web コンテナーに依存することです。Spring Interceptor は Spring MVC フレームワークの一部であり、Spring MVC フレームワークによって実装されます。クライアントから送信されたリクエストは、最初に Servlet Filter を通過し、次に Spring Interceptor を通過し、最終的に特定のビジネス コードに到達します。以下に示すように、リクエストの処理フローを説明するために絵を描きました。

ここに画像の説明を挿入

プロジェクトでは、Spring Interceptor をどのように使用しますか? 以下のような簡単なサンプルコードを書きました。LogInterceptor で実装されている機能は、先ほどの LogFilter とまったく同じですが、実装方法が少し異なります。LogFilter の要求と応答のインターセプトは doFilter() の関数で実装されますが、LogInterceptor の要求のインターセプトは preHandle() で実装され、応答のインターセプトは postHandle() で実装されます。

public class LogInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("拦截客户端发送来的请求.");
    return true; // 继续后续的处理
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("拦截发送给客户端的响应.");
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("这里总是被执行.");
  }
}

//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/*"/>
       <bean class="com.xzg.cd.LogInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

同様に、Spring Interceptor の最下層がどのように実装されているかを分析してみましょう。

もちろん、これも Chain of Responsibility パターンに基づいて実装されています。そのうち、HandlerExecutionChain クラスは、責任連鎖モードのプロセッサ チェーンです。Tomcat の ApplicationFilterChain と比較すると、その実装はより明確なロジックを持ち、再帰を使用して実装する必要はありません。これは主に、リクエストと応答の傍受を 2 つの関数に分割するためです。HandlerExecutionChain のソースコードを以下に示します. 同様にコードを簡略化し、主要なコードのみを残しています.

public class HandlerExecutionChain {
 private final Object handler;
 private HandlerInterceptor[] interceptors;

 public void addInterceptor(HandlerInterceptor interceptor) {
  initInterceptorList().add(interceptor);
 }

 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = 0; i < interceptors.length; i++) {
    HandlerInterceptor interceptor = interceptors[i];
    if (!interceptor.preHandle(request, response, this.handler)) {
     triggerAfterCompletion(request, response, null);
     return false;
    }
   }
  }
  return true;
 }

 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = interceptors.length - 1; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    interceptor.postHandle(request, response, this.handler, mv);
   }
  }
 }

 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
   throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = this.interceptorIndex; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    try {
     interceptor.afterCompletion(request, response, this.handler, ex);
    } catch (Throwable ex2) {
     logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    }
   }
  }
 }
}

SpringフレームワークではDispatcherServletのdoDispatch()メソッドでリクエストを振り分け、実際の業務ロジックを実行する前後にHandlerExecutionChain内のapplyPreHandle()関数とapplyPostHandle()関数を実行してインターセプト機能を実装する。具体的なコードの実装は非常に単純で、自分で理解できるはずなので、ここには記載しません。気になる方は自分で調べてみてください。

キーレビュー

では、本日の内容は以上です。集中する必要があることをまとめて一緒に確認しましょう。

Chain of Responsibility モードは、フレームワーク開発でフレームワークのフィルター機能とインターセプター機能を実装するためによく使用されます。これにより、フレームワークのユーザーは、フレームワークのソース コードを変更することなく、新しいフィルター機能とインターセプト機能を追加できます。これは、前述の拡張に対してオープンで、変更に対してクローズであるという設計原則も反映しています。

今日は、Servlet Filter と Spring Interceptor の 2 つの実用的な例を通じて、フレームワーク開発で Chain of Responsibility パターンがどのように適用されるかを示します。ソース コードから、前のレッスンで一連の責任モデルの古典的なコード実装を示しましたが、実際の開発では、特定の問題に対処する必要があり、コードの実装は状況に応じて異なることもわかります。さまざまなニーズが変化しました。実際、これはすべての設計パターンに当てはまります。

クラスディスカッション

  1. 前にプロキシ モードについて説明したとき、Spring AOP はプロキシ モードに基づいて実装されていると述べました。実際のプロジェクト開発では、AOP を使用して、認証、電流制限、ロギングなどのアクセス制御機能を実装できます。本日は、Servlet Filter と Spring Interceptor を使用してアクセス制御を実装することもできると述べました。プロジェクト開発において、パーミッションなどのアクセス制御機能を実装するには、3 つ (AOP、Servlet Filter、Spring Interceptor) のどれを選択する必要がありますか? 参照基準はありますか?
  2. 前述のServlet FilterとSpring Interceptorの他に、Dubbo FilterとNetty ChannelPipelineも責任連鎖モードの実用的な適用例です.責任連鎖モードを使い慣れているフレームワークを見つけて、私のように分析してもらえますか?基礎となる実装はどうですか?

メッセージを残して、あなたの考えを私と共有してください。何かを得た場合は、この記事を友達と共有してください。

おすすめ

転載: blog.csdn.net/fegus/article/details/130519181