過濾器 Filter 與 攔截器 Interceptor 的區別

JadeTal發表於2022-12-22

引言

說起 Filter 與 Interceptor 的區別,相信很多同學第一感覺就是容易、簡單!

畢竟開發中這兩個元件使用頻率較高,用法也較簡單。然後真回答起來有答不出個所以然來,場面尷尬?,老丟臉了!

看著簡單,一答就錯,下面我們們先看結論!再做詳細解說!

結論

  1. 底層原理不同:Filter 是 基於 函式回撥 實現的; Interceptor 是基於 反射機制與動態代理 實現的。
  2. 使用範圍不同:Filter 是 Servlet規範 的介面,依賴web容器(Tomcat等),只能在web工程中使用;Interceptor 是 Spring的元件,不依賴web容器。
  3. 觸發時機不同:請求進入順序: Tomcat ==> Filter ==> Servlet ==> Interceptor ==> Controller。
  4. 攔截範圍不同:Filter 對進入容器的所有請求進行攔截;Interceptor 只會對Controller中請求或訪問static目錄下的資源請求進行攔截。
  5. 注入bean情況不同:Filter 中能正常注入其他bean; Interceptor 在 springcontext 之前載入,而 bean 由 Spring管理,所以註冊 Interceptor 前需要先手動注入 Interceptor ;
  6. 控制執行順序不同:實際開發中,使用的通常是多個 Filter 或 Interceptor 組成的 鏈;Filter 中 攔截的核心方法是 doFilter(), Filter 直接按順序執行;但是在 Interceptor 中存在 前置攔截方法 preHandle() 和 後置攔截方法 postHandle(),preHandle() 是順序執行的,而 postHandle() 是反順序執行的。

原理

函式回撥

函式回撥,簡稱回撥(callback),是指透過函式引數傳遞到其它程式碼的,某一塊可執行程式碼的引用。

Java中沒有指標,不能將函式名作為引數傳遞,只能透過反射、直接呼叫、介面呼叫、Lambda表示式等方法來實現函式回撥。這裡用Lambda表示式給大家做個演示:

請求類:

public class Request{
    public void send(CallBack callBack) {
        System.out.println("[Request]:傳送請求");
    }
}

回撥介面:

public interface CallBack {
    void processResponse();
}

測試類:

public class Main {
    public static void main(String[] args) {
        Request request = new Request();
        request.send(()-> System.out.println("[CallBack]:監聽到請求,進行處理響應"));
    }
}

注:想看看回撥其他寫法的可以看看這篇文章:Java回撥的四種寫法(反射、直接呼叫、介面呼叫、Lamda表示式) - 騰訊雲開發者社群-騰訊雲

過濾器Filter 與 攔截器 Interceptor 原理

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

這是一個自定義的過濾器,doFilter()方法中傳入了一個介面引數FilterChain,這就是一個介面呼叫的函式回撥。FilterChain介面中就只有一個回撥方法doFilter()。

Interceptor 的原理就是一個jdk的動態代理,這裡就不作演示了。

Interceptor 注入其他bean

實際開發中,通常透過實現 HandlerInterceptorAdapter 來自定義攔截器,而不是直接使用 HandlerInterceptor。

image

  • 造成testService為null的原因就是攔截器比springcontext先載入,從下面的程式碼中也可以看到,攔截器是手動直接加入到登錄檔表中的,所以使用 @Bean 註解又手動注入了一次攔截器。此時攔截器中就可以注入其他bean了。
@Configuration
public class GlobalWebAppConfigurer implements WebMvcConfigurer {

    /**
     * 將攔截器新增到登錄檔中
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor()).addPathPatterns("/**");
    }

    // 手動注入攔截器
    @Bean
    public MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }
}

Interceptor 執行順序

由spring mvc的原始碼決定的,在核心轉發器 DispatcherServlet 的 doDispatch 中,applyPreHandle()applyPostHandle()對攔截器陣列的呼叫順序是相反的。具體原始碼等寫到springmvc再分析。

相關文章