最後のクラスでは、責任連鎖モデルの原理と実装を学び、機密ワード フィルタリング フレームワークの例を通じて責任連鎖モデルの設計意図を実証しました。本質的には、ほとんどのデザイン パターンと同様に、コードを分離し、コードの複雑さに対処し、コードがオープンとクローズの原則を満たし、コードのスケーラビリティを向上させることです。
さらに、責任連鎖モデルは、フレームワークに拡張ポイントを提供するためにフレームワークの開発でよく使用され、フレームワークのユーザーがフレームワークのソース コードを変更せずに拡張ポイントに基づいて新しい機能を追加できることにも言及しました。実際、より具体的に言うと、責任連鎖パターンは、フレームワーク フィルターとインターセプターの開発に最もよく使用されます。今日は、Java 開発で一般的に使用される 2 つのコンポーネントであるサーブレット フィルターと Spring インターセプターを介した、フレームワーク開発におけるそのアプリケーションについて説明します。
それでは早速、今日の学習を正式に始めましょう!
サーブレットフィルター
サーブレット フィルタは、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) が含まれると述べました。サーブレット フィルターに対応して、javax.servlet.Filter はプロセッサ インターフェイス、FilterChain はプロセッサ チェーンです。次に、FilterChain の実装方法に焦点を当てましょう。
ただし、前に述べたように、サーブレットは単なる仕様であり、特定の実装は含まれていないため、サーブレットの 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() 関数のコード実装は非常に扱いにくく、実際には再帰呼び出しです。各フィルター (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 リクエストを傍受するために使用される概念であると考えることができます。
それらの違いは、サーブレット フィルタがサーブレット仕様の一部であり、その実装が Web コンテナに依存することです。Spring Interceptor は Spring MVC フレームワークの一部であり、Spring MVC フレームワークによって実装されます。クライアントから送信されたリクエストは、まずサーブレット フィルターを通過し、次に Spring インターセプターを通過し、最終的に特定のビジネス コードに到達します。以下に示すように、リクエストの処理フローを示す図を描きました。
プロジェクトでは 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 の最下層がどのように実装されているかを分析してみましょう。
もちろん、これも責任連鎖パターンに基づいて実装されます。このうち、HandlerExecutionChain クラスは、Chain of Responsibility パターンのプロセッサ チェーンです。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 モードは、フレームワークのフィルタ機能とインターセプタ機能を実装するためにフレームワーク開発でよく使用され、フレームワーク ユーザーがフレームワークのソース コードを変更せずに新しいフィルタリング機能とインターセプト機能を追加できるようにします。これは、前述した拡張にはオープンで、変更にはクローズであるという設計原則も反映しています。
今日は、サーブレット フィルターと Spring インターセプターの 2 つの実践的な例を通して、責任連鎖パターンがフレームワーク開発にどのように適用されるかを示します。ソース コードからは、前のレッスンで責任連鎖モデルの古典的なコード実装を示しましたが、実際の開発では依然として特定の問題に対処する必要があり、コード実装は状況に応じて異なることもわかります。さまざまなニーズが変化しました。実際、これはすべてのデザインパターンに当てはまります。
クラスディスカッション
- 先ほどプロキシ モードについて説明したときに、Spring AOP はプロキシ モードに基づいて実装されると述べました。実際のプロジェクト開発では、AOP を使用して認証、電流制限、ロギングなどのアクセス制御機能を実装できます。今日は、サーブレット フィルターと Spring インターセプターを使用してアクセス制御を実装できることを説明しました。プロジェクト開発では、パーミッションなどのアクセス制御機能を実装するには、3 つ (AOP、サーブレット フィルター、Spring インターセプター) のどれを選択する必要がありますか? 何か参考となる基準はあるのでしょうか?
- 前述のサーブレット フィルターとスプリング インターセプターに加えて、ダボ フィルターと Netty ChannelPipeline も責任チェーン モードの実際的な適用例です。使い慣れたフレームワークを見つけて責任チェーン モードを使用し、私のように分析していただけませんか基礎となる実装はどうなるのでしょうか?