spring中的過濾器與攔截器

過濾器 Filter

什麼是過濾器?

與 Servlet 類似,過濾器是一些 web 應用程序組件,能夠綁定到一個 web 應用程序中。可是與其餘 web 應用組件不一樣的是,過濾器是「鏈」在容器的處理過程當中的。這就意味着它們能夠在請求達到 servlet 以前對其進行訪問,也能夠在響應信息返回到客戶端以前對其進行攔截。這種訪問使得過濾器能夠檢查並修改請求和響應的內容。java

Filter 的接口方法有哪些?

init :web

Filter 的初始化,在 Servlet 容器建立過濾器實例的時候調用,以確保過濾器可以正常工做。在 init() 方法執行過程當中遇到以下問題時,web 容器將不會配置其可用 :spring

  • 拋出 ServletException
  • 沒有在 web 容器設定的時間內返回

doFilter :cookie

Filter 的核心方法,用於對每一個攔截到的請求作一些設定好的操做。app

典型用途以下:框架

  • 在 HttpServletRequest 到達 Servlet 以前,攔截客戶的 HttpServletRequest。
  • 根據須要檢查 HttpServletRequest ,也能夠修改 HttpServletRequest 頭和數據。
  • 在 HttpServletResponse 到達客戶端以前,攔截 HttpServletResponse。
  • 根據須要檢查 HttpServletResponse,能夠修改 HttpServletResponse 頭和數據。

destory :ide

Filter 的銷燬,在 Servlet 容器銷燬過濾器實例時調用,以釋放其佔用的資源。只有在 doFilter() 方法中的全部線程退出或超時後,web 容器纔會調用此方法。函數

如何實現一個本身的 Filter

首先咱們須要建立一個類,讓它實現 Filter 接口,而後重寫接口中的方法:post

package com.demo.demofilter.demofilter.filter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@Order(1)   // 過濾順序,值越小越先執行
@WebFilter(urlPatterns = "/demoFilter", filterName = "filterTest")
public class DemoFilter 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("\ndoFilter()開始執行:發往 " + ((HttpServletRequest) servletRequest).getRequestURL().toString() + " 的請求已被攔截");

        System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());
        // filter的鏈式調用;將請求轉給下一條過濾鏈
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());

        System.out.println("doFilter()執行結束。\n");
    }

    @Override
    public void destroy() {
        System.out.println("filter銷燬中...");
    }
}
複製代碼

而後建立一個 Controller,對外提供兩條請求路徑:性能

package com.demo.demofilter.demofilter.filter;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("demoFilter")
public class FilterController {

    @GetMapping("hello")
    public String hello() {
        System.out.println("接口被調用:hello() ");
        return "hello filter";
    }

    @GetMapping("hi")
    public String hi() {
        System.out.println("接口被調用:hi()");
        return "hi filter";
    }
}
複製代碼

啓動項目,能夠看到咱們的過濾器已經隨着程序的啓動被成功初始化了

分別對這兩個接口發送請求:

最後使項目中止運行,則過濾器隨之銷燬

什麼是 Filter 的鏈式調用?

當咱們配置了多個 filter,且一個請求可以被屢次攔截時,該請求將沿着 客戶端 -> 過濾器1 -> 過濾器2 -> servlet -> 過濾器2 -> 過濾器1 -> 客戶端 鏈式流轉,以下圖所示 :

以上面的代碼爲例,因爲咱們只定義了一個過濾器,在執行到 filterChain.doFilter(servletRequest, servletResponse); 的時候,請求就會被直接轉送到 servlet 中進行調用。

因此咱們須要稍微給它改造一下,看看再添加一個 DemoFilter2 會發生什麼:

package com.demo.demofilter.demofilter.filter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@Order(2)   // 過濾順序,值越小越先執行,值相同或不指定時按filterName排序
// 注意這裏的urlPatterns要與前面保持一致
@WebFilter(urlPatterns = "/demoFilter", filterName = "filterTest2") 
public class DemoFilter2 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter2初始化中...");
    }

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

        System.out.println("\ndoFilter2()開始執行:發往 " + ((HttpServletRequest) servletRequest).getRequestURL().toString() + " 的請求已被攔截");

        System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("檢驗接口是否被調用,嘗試獲取contentType以下: " + servletResponse.getContentType());

        System.out.println("doFilter2()執行結束。\n");
    }

    @Override
    public void destroy() {
        System.out.println("filter2銷燬中...");
    }
}
複製代碼

運行結果以下:

能夠看出,當請求同時知足多個過濾器的過濾條件時,filterChain.doFilter() 會將其按必定順序(能夠經過 @Order 指定)依次傳遞到下一個 filter,直到進入 servlet 進行接口的實際調用。調用完成後,響應結果將沿着原路返回,並在再一次通過各個 filter 後,最終抵達客戶端。

攔截器 Interceptor

什麼是攔截器?

攔截器是 AOP 的一種實現策略,用於在某個方法或字段被訪問前對它進行攔截,而後在其以前或以後加上某些操做。同 filter 同樣,interceptor 也是鏈式調用。每一個 interceptor 的調用會依據它的聲明順序依次執行。通常來講攔截器能夠用於如下方面 :

  • 日誌記錄 :概率請求信息的日誌,以便進行信息監控、信息統計等等
  • 權限檢查 :對用戶的訪問權限,認證,或受權等進行檢查
  • 性能監控 :經過攔截器在進入處理器先後分別記錄開始時間和結束時間,從而獲得請求的處理時間
  • 通用行爲 :讀取 cookie 獲得用戶信息並將用戶對象放入請求頭中,從而方便後續流程使用

攔截器的接口方法有哪些?

preHandler:

方法的前置處理,將在請求處理以前被調用。通常用它來對方法進行一些前置初始化操做,或是對當前請求作一些預處理;此外也能夠用來進行權限校驗之類的判斷,來決定請求是否要繼續進行下去。

該方法返回一個布爾值,若該值爲 false,則請求結束,後續的 Interceptor 和 Controller 都不會再執行;若該值爲 true,則會繼續調用下一個 Interceptor 的 preHandler() 方法,若是已經到達最後一個 interceptor 了,就會調用當前請求的 Controller。

postHandler:

方法的後置處理,將在請求處理以後被調用。雖然是在 Controller 方法調用後再執行,但它的調用依然在 DispatcherServlet 進行視圖渲染並返回以前,因此通常能夠經過它對 Controller 處理以後的 ModelAndView 對象進行操做。

afterCompletion:

在整個請求處理完成(包括視圖渲染)後執行,主要用來進行一些資源的清理工做。

如何實現一個本身的攔截器

一樣,首先建立一個類,讓它實現 HandlerInterceptor 接口,而後重寫接口中的方法 :

package com.demo.demofilter.demofilter.interceptor;

import org.springframework.lang.Nullable;
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;

@Component
public class DemoInterceptor implements HandlerInterceptor {

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

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

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("afterHandle");
    }
}
複製代碼

緊接着須要對攔截器進行註冊,指明使用哪一個攔截器,及該攔截器對應攔截的 URL :

package com.demo.demofilter.demofilter.interceptor;

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

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 若是有多個攔截器,繼續registry.add往下添加就能夠啦
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/demoInterceptor/**");
    }

}
複製代碼

最後是 Controller

package com.demo.demofilter.demofilter.interceptor;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("demoInterceptor")
public class InterceptorController {

    @GetMapping("hello")
    public String hello() {
        System.out.println("接口被調用:hello() ");
        return "hello interceptor";
    }

    @GetMapping("hi")
    public String hi() {
        System.out.println("接口被調用:hi()");
        return "hi interceptor";
    }
}
複製代碼

運行結果以下 :

其餘注意事項

在 Http 的請求執行過程當中,要通過如下幾個步驟 :

  1. 由 DispatcherServlet 捕獲請求
  2. DispatcherServlet 將接收到的 URL 和對應的 Controller 進行映射
  3. 在請求到達相應的 Controller 以前,由攔截器對請求進行處理
  4. 處理完成以後,進行視圖的解析
  5. 返回視圖

因此,只有通過 DispatcherServlet 的請求才會被攔截器捕獲,而咱們自定義的 Servlet 請求則不會被攔截的。

過濾器與攔截器

做爲AOP思想的兩種典型實現,過濾器與攔截器有着許多類似的地方。而二者最大的區別在於 :過濾器是在 Servlet 規範中定義的,是由 Servlet 容器支持的;攔截器是在 Spring 容器內的,由 Spring 框架支持。

所以,做爲 Spring 的一個組件,攔截器能夠經過IOC容器進行管理,獲取其中的各個 bean 實例,對 spring 中的各類資源、對象,如 Service 對象、數據源、事務管理等進行調用;而過濾器則不能。

總的來講,二者主要在以下方面存在着差別 :

  • 過濾器是基於函數的回調,而攔截器是基於 Java 反射機制的
  • 過濾器能夠修改 request,而攔截器則不能
  • 過濾器須要在 servlet 容器中實現,攔截器能夠適用於 JavaEE、JavaSE 等各類環境
  • 攔截器能夠調用 IOC 容器中的各類依賴,而過濾器不能
  • 過濾器只能在請求的先後使用,而攔截器能夠詳細到每一個方法

最後補兩張圖嘿嘿 :

  1. filter、servlet、interceptor 觸發時機

    過濾器的觸發時機是在容器後,servlet 以前,因此過濾器的 doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 的入參是 ServletRequest,而不是 HttpServletRequest,由於過濾器是在 HttpServlet 以前。

  2. 過濾器攔截器運行前後步驟

    第二步中,SpringMVC 的機制是由 DispaterServlet 來分發請求給不一樣的 Controller,其實這一步是在 Servlet 的 service() 方法中執行的。
相關文章
相關標籤/搜索