Spring過濾器與攔截器

Spring 過濾器

什麼是過濾器

過濾器 Filter,是在 Servlet 規範中定義的,是 Servlet 容器支持的,該接口定義在 javax.servlet包下,主要是在客戶端請求(HttpServletRequest)進行預處理,以及對服務器響應(HttpServletResponse)進行後處理。接口代碼以下:java

package javax.servlet;

import java.io.IOException;

public interface Filter {
    void init(FilterConfig var1) throws ServletException;

    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    void destroy();
}

複製代碼

對上面三個接口方法進行分析:web

  • init(FilterConfig): 初始化接口,在用戶自定義的 Filter 初始化時被調用,它與 Servlet 的 init 方法的做用是同樣的。
  • doFilter(ServletRequest,ServletResponse,FilterChain): 在每一個用戶的請求進來時這個方法都會被調用,並在 Servlet 的 service 方法以前調用(若是咱們是開發 Servlet 項目),而 FilterChain 就表明當前的整個請求鏈,經過調用 FilterChain.doFilter能夠將請求繼續傳遞下去,若是想攔截這個請求,能夠不調用 FilterChain.doFilter,那麼這個請求就直接返回了,因此 Filter 是一種責任鏈設計模式,在spring security就大量使用了過濾器,有一條過濾器鏈。
  • destroy: 當 Filter 對象被銷燬時,這個方法被調用,注意,當 Web 容器調用這個方法以後,容器會再調用一次 doFilter 方法。

如何實現本身的過濾器

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

package com.example.demojava.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 Filter1 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("doFilter()開始執行:發往 " + ((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()執行結束。");

    }

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

複製代碼

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

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

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

        // filter的鏈式調用;將請求轉給下一條過濾鏈
        filterChain.doFilter(servletRequest, servletResponse);

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

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

    }

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

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

@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";
    }
}
複製代碼

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

分別對這兩個接口發送請求, 看到結果:服務器

doFilter()開始執行:發往 http://localhost:8080/demoFilter/hi 的請求已被攔截
檢驗接口是否被調用,嘗試獲取contentType以下: null

doFilter2()開始執行:發往 http://localhost:8080/demoFilter/hi 的請求已被攔截
檢驗接口是否被調用,嘗試獲取contentType以下: null

接口被調用:hi()

檢驗接口是否被調用,嘗試獲取contentType以下: text/plain;charset=UTF-8
doFilter2()執行結束。

檢驗接口是否被調用,嘗試獲取contentType以下: text/plain;charset=UTF-8
doFilter()執行結束。
複製代碼

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

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

攔截器

什麼是攔截器

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

  • 日誌記錄 :概率請求信息的日誌,以便進行信息監控、信息統計等等

  • 權限檢查 :對用戶的訪問權限,認證,或受權等進行檢查

  • 性能監控 :經過攔截器在進入處理器先後分別記錄開始時間和結束時間,從而獲得請求的處理時間

  • 通用行爲 :讀取 cookie 獲得用戶信息並將用戶對象放入請求頭中,從而方便後續流程使用

攔截器的接口方法

在 SpringMVC 中,DispatcherServlet 捕獲每一個請求,在到達對應的 Controller 以前,請求能夠被攔截器處理,在攔截器中進行前置處理後,請求最終纔到達 Controller。

攔截器的接口是 org.springframework.web.servlet.HandlerInterceptor接口,接口代碼以下:

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

複製代碼

接口方法解讀:

  • preHandle 方法:對客戶端發過來的請求進行前置處理,若是方法返回 true,繼續執行後續操做,若是返回 false,執行中斷請求處理,請求不會發送到 Controller
  • postHandler 方法:在請求進行處理後執行,也就是在 Controller 方法調用以後處理,固然前提是以前的 preHandle方法返回 true。具體來講,postHandler方法會在 DispatcherServlet 進行視圖返回渲染前被調用,也就是說咱們能夠在這個方法中對 Controller 處理以後的ModelAndView對象進行操做
  • afterCompletion 方法: 該方法在整個請求結束以後執行,固然前提依然是 preHandle方法的返回值爲 true 才行。該方法通常用於資源清理工做

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

一樣,首先建立一個類,讓它實現 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";
    }
}
複製代碼
複製代碼

運行結果以下 :

preHandler
接口被調用: hello()
postHandler
afterHandler
複製代碼

其餘注意事項

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

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

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

過濾器與攔截器二者對比總結

  1. 過濾器是基於函數的回調,而攔截器是基於 Java 反射機制的
  2. 過濾器 Filter 依賴於 Servlet 容器。攔截器 Interceptor 依賴於框架容器,能夠調用 IOC 容器中的各類依賴
  3. 攔截器能夠 preHandle方法內返回 false 進行中斷。過濾器就比較複雜,須要處理請求和響應對象來引起中斷,須要額外的動做,好比將用戶重定向到錯誤頁面
  4. 過濾器只能在請求的先後使用,而攔截器能夠詳細到每一個方法

tomcat 容器中執行順序: Filter -> Servlet -> Interceptor -> Controller

最後

本文到此結束,感謝閱讀。若是您以爲不錯,請關注公衆號【當我趕上你】,您的支持是我寫做的最大動力。

相關文章
相關標籤/搜索