過濾器(Filter)和攔截器(Interceptor)的執行順序和區別


測試代碼:https://github.com/kuotian/TestSpring/tree/master/01test_filter_interceptor

首先建立一個SpringBoot工程,增加一個IndexController供測試使用。

@Controller
public class IndexController {

    @GetMapping("/hello")
    @ResponseBody
    public String index(){
        return "hello";
    }
}

過濾器 Filter

過濾器( Filter),是 JavaEE 的標准,依賴於 Servlet 容器,使用的時候是配置在 SpringMVC 框架中是配置在web.xml 文件中的,可以配置多個,執行的順序是根據配置順序從上到下。在 SpringBoot 項目中也可以采用注解的形式實現。

Servlet中的過濾器Filter是實現了javax.servlet.Filter接口的服務器端程序,主要的用途是設置字符集、控制權限、控制轉向、做一些業務邏輯判斷等。其工作原理是,只要你在web.xml文件配置好要攔截的客戶端請求,它都會幫你攔截到請求,此時你就可以對請求或響應(Request、Response)統一配置請求編碼以及過濾一些非法參數,垃圾信息,簡化操作;同時還可進行邏輯判斷,如用戶是否已經登陸、有沒有權限訪問該頁面等等工作。它是隨你的web應用啟動而啟動的,只初始化一次,以后就可以攔截相關請求,只有當你的web應用停止或重新部署的時候才銷毀。

Filter可以認為是Servlet的一種“加強版”,它主要用於對用戶請求進行預處理,也可以對HttpServletResponse進行后處理,是個典型的處理鏈。Filter也可以對用戶請求生成響應,這一點與Servlet相同,但實際上很少會使用Filter向用戶請求生成響應。使用Filter完整的流程是:Filter對用戶請求進行預處理,接着將請求交給Servlet進行處理並生成響應,最后Filter再對服務器響應進行后處理。

Filter有如下幾個用處。
  • 在HttpServletRequest到達Servlet之前,攔截客戶的HttpServletRequest。
  • 根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和數據。
  • 在HttpServletResponse到達客戶端之前,攔截HttpServletResponse。
  • 根據需要檢查HttpServletResponse,也可以修改HttpServletResponse頭和數據。
Filter有如下幾個種類。
  • 用戶授權的Filter:Filter負責檢查用戶請求,根據請求過濾用戶非法請求。
  • 日志Filter:詳細記錄某些特殊的用戶請求。
  • 負責解碼的Filter:包括對非標准編碼的請求解碼。
  • 能改變XML內容的XSLT Filter等。
  • Filter可以負責攔截多個請求或響應;一個請求或響應也可以被多個Filter攔截。

javax.servlet.Filter接口

創建Filter必須實現javax.servlet.Filter接口,在該接口中定義了如下三個方法。

  • void init(FilterConfig config):用於完成Filter的初始化。
  • void destory():用於Filter銷毀前,完成某些資源的回收。
  • void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):實現過濾功能,該方法就是對每個請求及響應增加的額外處理。該方法可以實現對用戶請求進行預處理(ServletRequest request),也可實現對服務器響應進行后處理(ServletResponse response)—它們的分界線為是否調用了chain.doFilter(),執行該方法之前,即對用戶請求進行預處理;執行該方法之后,即對服務器響應進行后處理。

1.通過@WebFilter 注解配置

用這種方式配置TestFilter1、TestFilter2

@WebFilter(urlPatterns = "/hello")
@Order(4) //預期order值越小,過濾器越靠前,此處配置無效
public class TestFilter1 implements Filter {
    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
        System.out.println("##############Filter1 init##############");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //在DispatcherServlet之前執行
        System.out.println("##############doFilter1 before##############");
        filterChain.doFilter(servletRequest, servletResponse);
        // 在視圖頁面返回給客戶端之前執行,但是執行順序在Interceptor之后
        System.out.println("##############doFilter1 after##############");
    }

    @Override
    public void destroy() {
        System.out.println("##############Filter1 destroy##############");
    }
}

並且在啟動類添加 @ServletComponentScan

@SpringBootApplication
@ServletComponentScan
public class TestbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestbootApplication.class, args);
    }
}

2.通過@Bean來配置

用這種方式配置TestFilter3、TestFilter4

創建過濾器。

@Component
public class TestFilter3 implements Filter{
    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
        System.out.println("##############Filter3 init##############");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //在DispatcherServlet之前執行
        System.out.println("##############doFilter3 before##############");
        filterChain.doFilter(servletRequest, servletResponse);
        // 在視圖頁面返回給客戶端之前執行,但是執行順序在Interceptor之后
        System.out.println("##############doFilter3 after##############");
    }

    @Override
    public void destroy() {
        System.out.println("##############Filter3 destroy##############");
    }
}

注冊過濾器

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean testFilter3RegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean(new TestFilter3());
        registration.addUrlPatterns("/hello");
        registration.setOrder(1); // 值越小越靠前,此處配置有效
        return registration;
    }

    @Bean
    public FilterRegistrationBean testFilter4RegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean(new TestFilter4());
        registration.addUrlPatterns("/hello");
        registration.setOrder(2);
        return registration;
    }
}

3.Spring MVC在web.xml配置

創建一個Filter只需兩個步驟

  1. 創建Filter處理類
  2. web.xml文件中配置Filter

示例:

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

過濾器啟動測試

不使用order結果:

##############Filter4 init##############
##############Filter3 init##############
##############Filter2 init##############
##############Filter1 init##############
##############doFilter3 before##############
##############doFilter4 before##############
##############doFilter1 before##############
##############doFilter2 before##############
##############doFilter2 after##############
##############doFilter1 after##############
##############doFilter4 after##############
##############doFilter3 after##############

配置order,值越小越靠前

Filter Order
TestFilter1 4(注解@Order配置)
TestFilter2 3(注解@Order配置)
TestFilter3 2
TestFilter4 1
##############Filter4 init##############
##############Filter3 init##############
##############Filter2 init##############
##############Filter1 init##############
##############doFilter4 before##############
##############doFilter3 before##############
##############doFilter1 before##############
##############doFilter2 before##############
##############doFilter2 after##############
##############doFilter1 after##############
##############doFilter3 after##############
##############doFilter4 after##############

由此可見,@Order的順序配置沒有起作用,registration.setOrder()是有效的。

攔截器 Interceptor

攔截器(Interceptor) 不依賴 Servlet 容器,依賴 Spring 等 Web 框架,在 SpringMVC 框架中是配置在SpringMVC 的配置文件中,在 SpringBoot 項目中也可以采用注解的形式實現。

攔截器是 AOP 的一種應用,底層采用 Java 的反射機制來實現的。與過濾器一個很大的區別是在攔截器中可以注入 Spring 的 Bean,能夠獲取到各種需要的 Service 來處理業務邏輯,而過濾器則不行。

HandlerInterceptor接口:

  • preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顧名思義,該方法將在請求處理之前進行調用。SpringMVC 中的Interceptor 是鏈式的調用的,在一個應用中或者說是在一個請求中可以同時存在多個Interceptor 。每個Interceptor 的調用會依據它的聲明順序依次執行,而且最先執行的都是Interceptor 中的preHandle 方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求的一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值Boolean類型的,當它返回為false 時,表示請求結束,后續的Interceptor 和Controller 都不會再執行;當返回值為true 時就會繼續調用下一個Interceptor 的preHandle 方法,如果已經是最后一個Interceptor 的時候就會是調用當前請求的Controller 方法。

  • postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,由preHandle 方法的解釋我們知道這個方法包括后面要說到的afterCompletion 方法都只能是在當前所屬的Interceptor 的preHandle 方法的返回值為true 時才能被調用。postHandle 方法,顧名思義就是在當前請求進行處理之后,也就是Controller 方法調用之后執行,但是它會在DispatcherServlet 進行視圖返回渲染之前被調用,所以我們可以在這個方法中對Controller 處理之后的ModelAndView 對象進行操作。postHandle 方法被調用的方向跟preHandle 是相反的,也就是說先聲明的Interceptor 的postHandle 方法反而會后執行。

  • afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,該方法也是需要當前對應的Interceptor 的preHandle 方法的返回值為true 時才會執行。顧名思義,該方法將在整個請求結束之后,也就是在DispatcherServlet 渲染了對應的視圖之后執行。這個方法的主要作用是用於進行資源清理工作的。

攔截器的使用

interceptor 的執行順序大致為:

  1. 請求到達 DispatcherServlet
  2. DispatcherServlet 發送至 Interceptor ,執行 preHandle
  3. 請求達到 Controller
  4. 請求結束后,postHandle 執行

Spring 中主要通過 HandlerInterceptor 接口來實現請求的攔截,實現 HandlerInterceptor 接口需要實現下面三個方法:

  • preHandle() – 在handler執行之前,返回 boolean 值,true 表示繼續執行,false 為停止執行並返回。
  • postHandle() – 在handler執行之后, 可以在返回之前對返回的結果進行修改
  • afterCompletion() – 在請求完全結束后調用,可以用來統計請求耗時等等

配置實現攔截器

1.創建攔截器

自定義一個攔截器實現HandlerInterceptor,實現preHandle,postHandle,afterCompletion三個方法。

@Component
public class TestInterceptor1 implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("##############TestInterceptor1 preHandle##############");
        return true;
    }

    //在Controller之后的DispatcherServlet之后執行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("##############TestInterceptor1 postHandle##############");
    }

    // 在頁面渲染完成之后返回給客戶端執行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("##############TestInterceptor1 afterCompletion##############");
    }
}
2. 注冊攔截器

自定義攔截器配置類繼承自WebMvcConfigurer,重寫addInterceptors將自定義的攔截器添加至注冊中心。

@Component
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private TestInterceptor1 testInterceptor1;

    @Autowired
    private TestInterceptor2 testInterceptor2;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(testInterceptor1)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .addPathPatterns("/hello");

        registry.addInterceptor(testInterceptor2).addPathPatterns("/hello");
    }

}

注冊順序即為執行順序。

3.Spring MVC使用mvc:interceptors標簽

示例:

<mvc:interceptors>  
<!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 -->  
<bean class="com.hdu.interceptor.TestInterceptor"/>  
    <mvc:interceptor>  
         <mvc:mapping path="/**"/>  
         <mvc:exclude-mapping path="/parent/**"/>  
         <bean class="com.hdu.interceptor.TestInterceptor" />  
    </mvc:interceptor>  
</mvc:interceptors>

過濾器攔截器啟動測試

執行順序如下。

##############doFilter4 before##############
##############doFilter3 before##############
##############doFilter1 before##############
##############doFilter2 before##############
##############TestInterceptor1 preHandle##############
##############TestInterceptor2 preHandle##############
Index Controller
##############TestInterceptor2 postHandle##############
##############TestInterceptor1 postHandle##############
##############TestInterceptor2 afterCompletion##############
##############TestInterceptor1 afterCompletion##############
##############doFilter2 after##############
##############doFilter1 after##############
##############doFilter3 after##############
##############doFilter4 after##############

執行順序


區別

Spring的Interceptor(攔截器)與Servlet的Filter有相似之處,比如二者都是AOP編程思想的體現,都能實現權限檢查、日志記錄等。不同的是:

攔截器是 AOP 的一種應用,底層采用 Java 的反射機制來實現的。與過濾器一個很大的區別是在攔截器中可以注入 Spring 的 Bean,能夠獲取到各種需要的 Service 來處理業務邏輯,而過濾器則不行。

Filter Interceptor Summary
Filter 接口定義在 javax.servlet 包中 接口 HandlerInterceptor 定義在org.springframework.web.servlet 包中
Filter 定義在 web.xml 中
Filter在只在 Servlet 前后起作用。Filters 通常將 請求和響應(request/response) 當做黑盒子,Filter 通常不考慮servlet 的實現。 攔截器能夠深入到方法前后、異常拋出前后等,因此攔截器的使用具有更大的彈性。允許用戶介入(hook into)請求的生命周期,在請求過程中獲取信息,Interceptor 通常和請求更加耦合。 在Spring構架的程序中,要優先使用攔截器。幾乎所有 Filter 能夠做的事情, interceptor 都能夠輕松的實現
Filter 是 Servlet 規范規定的。 而攔截器既可以用於Web程序,也可以用於Application、Swing程序中。 使用范圍不同
Filter 是在 Servlet 規范中定義的,是 Servlet 容器支持的。 而攔截器是在 Spring容器內的,是Spring框架支持的。 規范不同
Filter 不能夠使用 Spring 容器資源 攔截器是一個Spring的組件,歸Spring管理,配置在Spring文件中,因此能使用Spring里的任何資源、對象,例如 Service對象、數據源、事務管理等,通過IoC注入到攔截器即可 Spring 中使用 interceptor 更容易
Filter 是被 Server(Tomcat等) 調用 Interceptor 是被 Spring 調用 因此Filter總是優先於Interceptor執行

總結:

  • Filter需要在web.xml中配置,依賴於Servlet;

  • Interceptor需要在SpringMVC中配置,依賴於框架;

  • 兩者的本質區別:攔截器(Interceptor)是基於Java的反射機制,而過濾器(Filter)是基於函數回調。從靈活性上說攔截器功能更強大些,Filter能做的事情,都能做,而且可以在請求前,請求后執行,比較靈活。Filter主要是針對URL地址做一個編碼的事情、過濾掉沒用的參數、安全校驗(比較泛的,比如登錄不登錄之類),太細的話,還是建議用interceptor。不過還是根據不同情況選擇合適的。

引用資料:

https://blog.csdn.net/javageektech/article/details/94250382

https://www.cnblogs.com/junzi2099/p/8022058.html#_label0

https://blog.csdn.net/zxd1435513775/article/details/80556034


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM