Spring Boot2(七):攔截器和過濾器

一、前言

過濾器和攔截器兩者都具有AOP的切面思想,關於aop切面,可以看上一篇文章。過濾器filter和攔截器interceptor都屬於面向切面編程的具體實現。

二、過濾器

過濾器工作原理

1

從上圖可以看出,當瀏覽器發送請求到服務器時,先執行過濾器,然後才訪問Web資源。服務器響應Response,從Web資源抵達瀏覽器之前,也會途徑過濾器。

過濾器是一個實現javax.servlet.Filter接口的Java類。javax.servlet.Filter接口定義了三個方法

方法 描述
public void init(FilterConfig filterConfig) web 應用程序啓動時,web 服務器將創建Filter 的實例對象,並調用其init方法,讀取web.xml配置,完成對象的初始化功能,從而爲後續的用戶請求作好攔截的準備工作(filter對象只會創建一次,init方法也只會執行一次)。開發人員通過init方法的參數,可獲得代表當前filter配置信息的FilterConfig對象。
public void doFilter (ServletRequest, ServletResponse, FilterChain) 該方法完成實際的過濾操作,當客戶端請求方法與過濾器設置匹配的URL時,Servlet容器將先調用過濾器的doFilter方法。FilterChain用戶訪問後續過濾器。
public void destroy() Servlet容器在銷燬過濾器實例前調用該方法,在該方法中釋放Servlet過濾器佔用的資源。

SpringBoot摒棄了繁瑣的xml配置的同時,提示了幾種註冊組件:ServletRegistrationBean,
FilterRegistrationBean,ServletListenerRegistrationBean,DelegatingFilterProxyRegistrationBean,用於註冊自對應的組件,如過濾器,監聽器等。

代碼實現

1、添加maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--devtools熱部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
</dependency>

2、添加攔截器

@Configuration
public class WebConfig {
    @Bean
    public RemoteIpFilter remoteIpFilter() {
        return new RemoteIpFilter();
    }
    
    /**
     * 註冊第三方過濾器
     * 功能與spring mvc中通過配置web.xml相同
     * 可以添加過濾器鎖攔截的 URL,攔截更加精準靈活
     * @return
     */
    @Bean
    public FilterRegistrationBean testFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new MyFilter());
        // 過濾應用程序中所有資源,當前應用程序根下的所有文件包括多級子目錄下的所有文件,注意這裏*前有“/”
        registration.addUrlPatterns("/*");
        registration.addInitParameter("paramName", "paramValue");
        registration.setName("MyFilter");
        // 過濾器順序
        registration.setOrder(1);
        return registration;
    }

    // 定義過濾器
    public class MyFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("init");
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            System.out.println("this is MyFilter,url :" + request.getRequestURI());
            filterChain.doFilter(servletRequest, servletResponse);
        }

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

3、controller層

@RestController
public class HelloController {

    @GetMapping("/filter")
    public String testFilter(){
        return "filter is ok";
    }
}

4、測試

通過發送post請求:127.0.0.1:8081/filter

查看日誌可以看到過濾器已經開始工作了。

測試截圖Filter

三、攔截器

攔截器概念

不同於過濾器,具體區別我們下面再將,先講一講攔截器實現的機制。

在AOP(Aspect-Oriented Programming)中用於在某個方法或字段被訪問之前,進行攔截,然後在之前或之後加上某些操作。攔截是AOP的一種實現策略。

攔截器作用

有什麼作用呢?AOP面向切面有什麼作用,那麼攔截器就有什麼作用。

  • 日誌記錄:記錄請求信息的日誌,以便進行信息監控、信息統計、計算PV...
  • 權限檢查:認證或者授權等檢查
  • 性能監控:通過攔截器在進入處理器之前記錄開始時間,處理完成後記錄結束時間,得到請求處理時間。
  • 通用行爲:讀取cookie得到用戶信息並將用戶對象放入請求頭中,從而方便後續流程使用。

攔截器實現

攔截器集成接口HandlerInterceptor,實現攔截,接口方法有下面三種:

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

  2. postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    在當前請求進行處理之後,也就是Controller 方法調用之後執行,但是它會在DispatcherServlet 進行視圖返回渲染之前被調用,所以我們可以在這個方法中對Controller 處理之後的ModelAndView 對象進行操作。

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

總結一點就是:

preHandle是請求執行前執行

postHandle是請求結束執行

afterCompletion是視圖渲染完成後執行

代碼實現

1、添加Maven依賴

和過濾器一樣

2、添加攔截器類

其中LogInterceptor實現HandlerInterceptor接口的三個方法,同時需要preHandle返回true,該方法通常用於清理資源等工作。

主方法繼承WebMvcConfigurer

注意不用用WebMvcConfigurerAdapter,該方法已經被官方標註過時了,在java8是默認實現的。

所以我們需要使用的是WebMvcConfigurer進行靜態資源的配置。

配置的主要有兩項:一個是制定攔截器,第二個是指定攔截的URL

@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    /**
     * 攔截器註冊類
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
    }
    /**
     * 定義攔截器
     */
    public class LogInterceptor implements HandlerInterceptor {
        long start = System.currentTimeMillis();

        /**
         * 請求執行前執行
         */
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
            log.info("preHandle");
            start = System.currentTimeMillis();
            return true;
        }
        /**
         * 請求結束執行
         */
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
            log.info("Interceptor cost="+(System.currentTimeMillis()-start));
        }
        /**
         * 視圖渲染完成後執行
         */
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
            log.info("afterCompletion");
        }
    }
}

3、controller層

@RestController
public class HelloController {

    @RequestMapping("/interceptor")
    public String home(){
        return "interceptor is ok";
    }
}

4、測試

可以看到,我們通過攔截器實現了同樣的功能。不過這裏還要說明一點的是,其實這個實現是有問題的,因爲preHandle和postHandle是兩個方法,所以我們這裏不得不設置一個共享變量start來存儲開始值,但是這樣就會存在線程安全問題。當然,我們可以通過其他方法來解決,比如通過ThreadLocal就可以很好的解決這個問題,有興趣的同學可以自己實現。不過通過這一點我們其實可以看到,雖然攔截器在很多場景下優於過濾器,但是在這種場景下,過濾器比攔截器實現起來更簡單。

測試截圖Interceptor

四、過濾器和攔截器的區別

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

不同的是:

  • 使用範圍不同:Filter是Servlet規範規定的,只能用於Web程序中。而攔截器既可以用於Web程序,也可以用於Application、Swing程序中。
  • 規範不同: Filter是在Servlet規範中定義的,是Servlet容器支持的。而攔截器是在Spring容器內的,是Spring框架支持的。
  • 使用的資源不同:同其他的代碼塊一樣,攔截器也是一個Spring的組件,歸Spring管理,配置在Spring文件中,因此能使用Spring裏的任何資源、對象,例如Service對象、數據源、事務管理等,通過loC注入到攔截器即可:而Filter則不能。
  • 深度不同:Filter在只在Servlet前後起作用。而攔截器能夠深入到方法前後、異常拋出前後等,因此攔截器的使用具有更大的彈性。所以在Spring構架的程序中,要優先使用攔截器。

五、總結

注意:過濾器的觸發時機是容器後,servlet之前,所以過濾器的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)的入參是ServletRequest,而不是HttpServletRequest,因爲過濾器是在HttpServlet之前。下面這個圖,可以讓你對Filter和Interceptor的執行時機有更加直觀的認識。

過濾器和攔截器關係圖

只有經過DispatcherServlet 的請求,纔會走攔截器鏈,自定義的Servlet請求是不會被攔截的,比如我們自定義的Servlet地址。

過濾器依賴於Servlet容器,而Interceptor則爲SpringMVC的一部分。過濾器能夠攔截所有請求,而Interceptor只能攔截Controller的請求,所以從覆蓋範圍來看,Filter應用更廣一些。但是在Spring逐漸一統Java框架、前後端分離越演越烈,實際上大部分的應用場景,攔截器都可以滿足了。

六、源碼

SpringBoot-過濾器spring-boot-16-filter

SpringBoot-攔截器spring-boot-17-interceptor

七、參考

SpringBoot實現過濾器、攔截器與切片
Spring Boot實戰:攔截器與過濾器
Spring Boot使用過濾器和攔截器分別實現REST接口簡易安全認證

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章