フィルターとインターセプターの 6 つの主な違い

        私は普段、知識のポイントは簡単だと思っていますが、あまり細かいことは気にせず、人に聞かれると説明できません。本当に見たらすぐに捨てられる内容です。みんなで練習に基づいてフィルターとインターセプターを区別しましょう~

一般的な理解:
(1) フィルター: たくさんのものがあるとき、要件を満たすものだけを選択したいと思います。これらの要件を定義するためのツールがフィルターです。(理解: 一連の文字から B を選択するだけです)
(2) インターセプター: プロセスの進行中に、その進行を妨害したり、場合によってはプロセスを終了したりする場合、これがインターセプターの役割です。(理解してください:これは単なる手紙の束です、それらに介入して、検証に合格する手紙の数を減らし、ところで他のことをしてください)

1. 準備作業

        プロジェクト内のフィルターとインターセプターを次のように構成します。

1.フィルター

フィルタの構成は比較的単純です。Filter インタフェースを直接実装できます。@WebFilter アノテーションを介して特定の URL をインターセプトすることもできます。Filter インタフェースが 3 つのメソッドを定義していることがわかります:

(1) init() : このメソッドは、コンテナがフィルターの初期化を開始するときに呼び出され、フィルターのライフサイクル全体で 1 回だけ呼び出されます。注: このメソッドは正常に実行される必要があります。正常に実行されない場合、フィルターは機能しません。

(2) doFilter() : このメソッドはコンテナ内のリクエストごとに呼び出され、次のフィルターを呼び出すために FilterChain が使用されます。

(3) destroy() : このメソッドは、コンテナがフィルター インスタンスを破棄するときに呼び出されます。通常、メソッド内でリソースが破棄またはクローズされます。フィルターのライフ サイクル全体で 1 回だけ呼び出されます。

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;


/**
 * @author: tangbingbing
 * @date: 2023/8/7 15:52
 */
@Component
public class MyFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("Filter 前置");
	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		System.out.println("Filter 处理中");
		filterChain.doFilter(servletRequest, servletResponse);
	}

	@Override
	public void destroy() {
		System.out.println("Filter 后置");
	}
}

2. インターセプター (インターセプター AOP アイデア)

        インターセプタはチェーン呼び出しです。アプリケーション内に複数のインターセプタが同時に存在できます。1 つのリクエストで複数のインターセプタをトリガーすることもでき、各インターセプタの呼び出しは、宣言された順序に従って順番に実行されます。

まず、単純なインターセプター処理クラスを作成します。リクエストのインターセプトは HandlerInterceptor を通じて実装されます。

このインターフェイスでは、次の 3 つのメソッドも定義されています。

(1) preHandle() : このメソッドはリクエストが処理される前に呼び出されます。注: このメソッドの戻り値が false の場合、現在のリクエストは終了したものとみなされ、自身のインターセプターが無効になるだけでなく、他のインターセプターも実行されなくなります。

(2) postHandle() : preHandle() メソッドの戻り値が true の場合のみ実行されます。これは、コントローラーでのメソッド呼び出しの後、DispatcherServlet がレンダリングされたビューに戻る前に呼び出されます。興味深いのは、postHandle() メソッドが preHandle() とは逆の順序で呼び出されることです。最初に宣言されたインターセプタの preHandle() メソッドが最初に実行され、postHandle() メソッドが後で実行されます。

(3) afterCompletion() : preHandle()メソッドの戻り値がtrueの場合のみ実行されます。リクエスト全体が完了すると、対応するビューをレンダリングした後に DispatcherServlet が実行されます。

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author: tangbingbing
 * @date: 2023/8/7 16:05
 */
@Component
public class MyInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		System.out.println("Interceptor 前置");
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		System.out.println("Interceptor 处理中");
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		System.out.println("Interceptor 后置");
	}
}

レジスタインターセプタ

カスタマイズされたインターセプター処理クラスを登録し、addPathPatterns や excludePathPatterns などの属性を通じてインターセプトまたは除外する必要がある URL を設定します。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author: tangbingbing
 * @date: 2023/8/7 16:12
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
	}
}

2. フィルターとインターセプターの 6 つの主な違い

1. 異なる実装原則

フィルターとインターセプターの基本的な実装方法はまったく異なり、フィルターは関数コールバックに基づいていますが、インターセプターは Java のリフレクション メカニズム (動的プロキシ) に基づいて実装されています。

ここではフィルターの分析に焦点を当てます

カスタム フィルターでは、FilterChain パラメーターを持つ doFilter() メソッドを実装しますが、実際にはコールバック インターフェイスです。ApplicationFilterChain はその実装クラスであり、この実装クラスにはコールバック メソッドである doFilter() メソッドも含まれています。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain でカスタマイズされた xxxFilter クラスを取得し、内部コールバック メソッド doFilter() でカスタマイズされた各 xxxFilter フィルターを呼び出し、doFilter() メソッドを実行できます。

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //获取第pos个filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

      各 xxxFilter は、最初に独自の doFilter() フィルタリング ロジックを実行し、最後に実行の終了前に filterChain.doFilter(servletRequest, servletResponse) を実行します。これにより、ApplicationFilterChain の doFilter() メソッドがコールバックされ、それによって関数のコールバックが周期的に実行されます。 。

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        filterChain.doFilter(servletRequest, servletResponse);
    }

2. 使用範囲の違い

        フィルターが javax.servlet.Filter インターフェースを実装しており、このインターフェースがサーブレット仕様で定義されていることがわかります。これは、フィルターの使用が Tomcat などのコンテナーに依存しており、フィルターが Web プログラムでのみ使用されることを意味します。

        Interceptor は Spring コンポーネントであり、Spring コンテナによって管理され、Tomcat などのコンテナに依存せず、単独で使用できます。Web プログラムだけでなく、アプリケーション、Swing などのプログラムでも使用できます。

3. 異なるトリガー時間

フィルターとインターセプターのトリガータイミングも異なりますので、下の図を見てみましょう。

 フィルタ リクエストがコンテナに入った後、サーブレットに入る前にフィルタが前処理され、サーブレットの処理後にリクエストが終了します。インターセプタは、リクエストがサーブレットに入った後、コントローラに入る前に前処理され、対応するビューがコントローラでレンダリングされた後にリクエストは終了します。

4. 傍受されるリクエストの範囲が異なります

次のように新しいコントローラーを作成します

@RestController
public class TestController {

	@RequestMapping("/test")
	public String test(){
		//System.out.println("源码环境构建成功...");
		System.out.println("Controller Method...");
		return "源码环境构建成功";
	}
}

プロジェクトの起動プロセス中に、フィルターinit()メソッドがコンテナーの起動とともに初期化されたことが判明しました。

 次のようにコントローラーインターフェイスコンソールをリクエストします

実行順序: フィルター処理 -> インターセプター前処理 -> コントローラー メソッド... -> インターセプター処理 -> インターセプター後処理

フィルターはFilter複数回実行できますが、インターセプターはInterceptor1 回だけ実行されます。これは、フィルターがコンテナーに入るほぼすべてのリクエストに作用するのに対し、インターセプターはController中間のリクエストまたはstaticディレクトリ内のリソースにアクセスするリクエストにのみ作用するためです。

5. 豆の注入状況が異なる

service実際のビジネスシナリオでは、フィルターやインターセプターを適用する際に、ビジネスロジックを処理するために何らかのサービスが導入されることは避けられません。

次に、それをフィルターとインターセプターの両方に注入して、service違いを見てみましょう。

/**
 * @author: tangbingbing
 * @date: 2023/8/7 17:24
 */
public interface TestService {
	void a();
}
/**
 * @author: tangbingbing
 * @date: 2023/8/7 17:24
 */
@Component
public class TestServiceImpl implements TestService {

	@Override
	public void a() {
		System.out.println("我是方法A");
	}
}

フィルターにサービスを挿入する

@Autowired
private TestService testService;
	
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		System.out.println("Filter 处理中");
		testService.a();
		filterChain.doFilter(servletRequest, servletResponse);
}

アクセスインターフェースコンソールは次のとおりです

 サービスをインターセプターに挿入し、リクエストを開始してテストすると、エラーが報告されます。デバッグ後、挿入されたサービスが Null であることがわかりました。

 これはロード順序に起因する問題で、インターセプタは springcontext より前にロードされ、Bean は Spring によって管理されます。

ps: この問題を解決するにはどうすればよいですか? 下がり続ける

解決策も非常に簡単で、インターセプターを登録する前に、まず手動でインターセプターを挿入します。注: registry.addInterceptor() に登録されるのは getMyInterceptor() インスタンスです。

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

	@Bean
	public MyInterceptor getMyInterceptor(){
		System.out.println("注入了MyInterceptor");
		return new MyInterceptor();
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		//registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
		registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
	}
}

再度インターフェースにアクセスしてコンソールを確認すると正常です。

 6. コントロールの実行順序が異なる

実際の開発プロセスでは、複数のフィルタやインターセプタが同時に存在することがありますが、実行順序に関係して、特定のフィルタやインターセプタを先に実行したい場合があります。

インターセプタのデフォルトの実行順序は登録順序です。これは@Orderアノテーションを介して手動で設定および制御することもできます。値が小さいほど、最初に実行されます。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
}
 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }

出力結果を見ると、最初に宣言したインターセプタのpreHandle()メソッドが先に実行され、postHandle()メソッドが後から実行されていることがわかりました。postHandle() メソッドが呼び出される順序は、実際には preHandle() の順序とは逆です。実際の開発において実行順序が厳密に要求される場合には、この点に特に注意を払う必要があります。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
Controller Method...
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后

では、なぜこのようなことが起こっているのでしょうか? 答えを得るには、ソース コードを見るしかありません。コントローラー内のすべてのリクエストはコア コンポーネント DispatcherServlet を介してルーティングされる必要があり、その doDispatch() メソッドが実行され、インターセプター postHandle() とその中で preHandle() メソッドが呼び出されます。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        try {
         ...........
            try {
           
                // 获取可以执行当前Handler的适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 注意: 执行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

applyPreHandle() と applyPostHandle() の 2 つのメソッドがどのように呼び出されるかを見ると、postHandle() と preHandle() の実行順序が逆になっている理由がわかります。

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

        return true;
    }
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.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);
            }
        }
    }

2 つのメソッドでインターセプタ配列 HandlerInterceptor[] を呼び出すと、ループの順序が実際には逆になり、postHandle() メソッドと preHandle() メソッドが逆の順序で実行されることが判明しました。

おすすめ

転載: blog.csdn.net/qq_45443475/article/details/132147949