1. 程式人生 > 實用技巧 >SpringBoot 過濾器,攔截器初步學習整理(有示例程式碼)

SpringBoot 過濾器,攔截器初步學習整理(有示例程式碼)

引言

關於兩者的理論知識,網上有太多就補貼在本章了。該文章主要以程式碼的形式說明,方便新手理解。
這裡也是新手學習時整理的文件,主要針對於新手的,如果有不正確的地方希望加一指正。

兩者的區別

  1. Filter 是基於 函式回撥的,而 Interceptor 則是基於 Java反射 和 動態代理。
  2. Filter 依賴於 Servlet 容器,而 Interceptor 它依賴於web框架。
  3. Filter 對幾乎 所有的請求 起作用,而 Interceptor 只對 Controller 對請求起作用。

執行順序

對於自定義 Servlet 對請求分發流程:

  1. Filter 過濾請求處理;
  2. Servlet 處理請求;
  3. Filter 過濾響應處理。

對於自定義 Controller 的請求分發流程:

  1. Filter 過濾請求處理:
  2. Interceptor 攔截請求處理;
  3. 對應的 HandlerAdapter 處理請求;
  4. Interceptor 攔截響應處理;
  5. Interceptor 的最終處理;
  6. Filter 過濾響應處理。

程式碼示例

過濾器

一個 Servlet 請求可以經由多個 Filter 進行過濾,最終由 Servlet 處理並響應客戶端。這裡就建兩個過濾器測試。

package com.blackcat.demo.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import java.io.IOException; import static org.springframework.util.ObjectUtils.isEmpty; /** * <p> 描述 :過濾器 * @author : blackcat * @date : 2020/5/20 14:04 * * Filter 對 使用者請求 進行 預處理,接著將請求交給 Servlet 進行 處理 並 生成響應, * 最後 Filter 再對 伺服器響應 進行 後處理。 * Filter 是可以複用的程式碼片段,常用來轉換 HTTP 請求、響應 和 頭資訊。 * Filter 不像 Servlet,它不能產生 響應,而是隻 修改 對某一資源的 請求 或者 響應。 * * 過濾器就是篩選出你要的東西,比如requeset中你要的那部分 * 一個 Servlet 請求可以經由多個 Filter 進行過濾,最終由 Servlet 處理並響應客戶端。
*/ @Slf4j @WebFilter(filterName = "firstIndexFilter",// filter名稱 displayName = "firstIndexFilter", urlPatterns = {"/index/*"},// 路徑匹配 initParams = @WebInitParam( name = "firstIndexFilterInitParam", value = "io.ostenant.springboot.sample.filter.FirstIndexFilter") ) public class FirstIndexFilter implements Filter { /** * <p> 描述 : 初始化時,會執行 init() 方法 * @author : blackcat * @date : 2020/5/20 14:16 */ @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("註冊新篩選器 {}", filterConfig.getFilterName()); } /** * <p> 描述 : 過濾請求 * @author : blackcat * @date : 2020/5/20 14:24 * @param request 未到達 Servlet 的 HTTP 請求; * @param response 由 Servlet 處理並生成的 HTTP 響應; * @param chain 過濾器鏈 物件,可以按順序註冊多個 過濾器。 * @return void * 每次請求路徑匹配 urlPatterns 配置的路徑時,就會進入 doFilter() 方法進行具體的 請求 和 響應過濾。 * 一個 過濾器鏈 物件可以按順序註冊多個 過濾器。符合當前過濾器過濾條件,即請求 過濾成功 直接放行,則交由下一個 過濾器 進行處理。 * 所有請求過濾完成以後,由 IndexHttpServlet 處理並生成 響應,然後在 過濾器鏈 以相反的方向對 響應 進行後置過濾處理。 */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("FirstIndexFilter預過濾請求"); // 當 HTTP 請求攜帶 filter1 引數時,請求會被放行;否則,直接 過濾中斷,結束請求處理。 String filter = request.getParameter("filter1"); if (isEmpty(filter)) { response.getWriter().println("請設定請求引數 \"filter1\""); log.info("請設定請求引數 filter1"+filter); return; } chain.doFilter(request, response); log.info("FirstIndexFilter對響應進行後篩選"); } @Override public void destroy() { log.info("登出過濾器 {}", getClass().getName()); } }
package com.blackcat.demo.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import java.io.IOException;

import static org.springframework.util.ObjectUtils.isEmpty;

/**
 * <p> 描述 :過濾器
 * @author : blackcat
 * @date : 2020/5/20 14:20
 *
 * 一個 Servlet 請求可以經由多個 Filter 進行過濾,最終由 Servlet 處理並響應客戶端。
 */
@Slf4j
@WebFilter(filterName = "secondIndexFilter",// filter名稱
        displayName = "secondIndexFilter",
        urlPatterns = {"/index/*"},// 路徑匹配
        initParams = @WebInitParam(
                name = "secondIndexFilterInitParam",
                value = "io.ostenant.springboot.sample.filter.SecondIndexFilter")
)
public class SecondIndexFilter implements Filter {

    /**
     * <p> 描述 : 初始化時,會執行 init() 方法
     * @author : blackcat
     * @date  : 2020/5/20 14:16
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("註冊新篩選器 {}", filterConfig.getFilterName());
    }

    /**
     * <p> 描述 : 過濾請求
     * @author : blackcat
     * @date  : 2020/5/20 14:24
     * @param request 未到達 Servlet 的 HTTP 請求;
     * @param response 由 Servlet 處理並生成的 HTTP 響應;
     * @param chain 過濾器鏈 物件,可以按順序註冊多個 過濾器。
     * @return void
     * 每次請求路徑匹配 urlPatterns 配置的路徑時,就會進入 doFilter() 方法進行具體的 請求 和 響應過濾。
     * 一個 過濾器鏈 物件可以按順序註冊多個 過濾器。符合當前過濾器過濾條件,即請求 過濾成功 直接放行,則交由下一個 過濾器 進行處理。
     * 所有請求過濾完成以後,由 IndexHttpServlet 處理並生成 響應,然後在 過濾器鏈 以相反的方向對 響應 進行後置過濾處理。
    */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("SecondIndexFilter預過濾請求");
        // 當 HTTP 請求攜帶 filter1 引數時,請求會被放行;否則,直接 過濾中斷,結束請求處理。
        String filter = request.getParameter("filter2");
        if (isEmpty(filter)) {
            response.getWriter().println("請設定請求引數 \"filter2\"");
            return;
        }
        chain.doFilter(request, response);
        log.info("SecondIndexFilter對響應進行後篩選");
    }

    @Override
    public void destroy() {
        log.info("登出過濾器 {}", getClass().getName());
    }
}

攔截器

類似面向切面程式設計中的切面通知,我們通過動態代理對一個 service() 方法新增通知進行功能增強。

比如說在方法執行前進行初始化處理,在方法執行後進行 後置處理。

攔截器 的思想和 AOP 類似,區別就是攔截器只能對 Controller 的 HTTP 請求進行攔截。

package com.blackcat.demo.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static org.springframework.util.ObjectUtils.isEmpty;

/**
 * <p> 描述 :攔截器
 * @author : blackcat
 * @date : 2020/5/20 14:28
 *
 * 類似 面向切面程式設計 中的 切面 和 通知,我們通過 動態代理 對一個 service() 方法新增 通知 進行功能增強。比如說在方法執行前進行 初始化處理,在方法執行後進行 後置處理。
 * 攔截器 的思想和 AOP 類似,區別就是 攔截器 只能對 Controller 的 HTTP 請求進行攔截。
 *
 * 攔截器在做安全方面用的比較多,比如終止一些流程
 * 攔截器 Interceptor 只對 Handler 生效。Spring MVC 會為 Controller 中的每個 請求方法 例項化為一個 Handler物件,
 * 由 HandlerMapping 物件路由請求到具體的 Handler,然後由 HandlerAdapter 通過反射進行請求 處理 和 響應,這中間就穿插著 攔截處理。
 */
@Slf4j
public class FirstIndexInterceptor implements HandlerInterceptor {

    /**
     * <p> 描述 : 在請求處理之前進行呼叫(Controller方法呼叫之前
     * @author : blackcat
     * @date  : 2020/5/20 14:32
     *
     * controller 接收請求、處理 request 之前執行,返回值為 boolean,
     * 返回值為 true 時接著執行 postHandle() 和 afterCompletion() 方法;
     * 如果返回 false 則 中斷 執行。
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("進入preHandle");
        String interceptor = request.getParameter("interceptor1");
        if (isEmpty(interceptor)) {
            response.getWriter().println("請設定請求引數 \"interceptor1\"");
            return false;
        }
        return true;
    }

    /**
     * <p> 描述 : 請求處理之後進行呼叫,但是在檢視被渲染之前(Controller方法呼叫之後)
     * @author : blackcat
     * @date  : 2020/5/20 14:33
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("進入postHandle");
    }

    /**
     * <p> 描述 : 在整個請求結束之後被呼叫,也就是在DispatcherServlet 渲染了對應的檢視之後執行(主要是用於進行資源清理工作)
     * @author : blackcat
     * @date  : 2020/5/20 14:33
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("進入afterCompletion");
    }
}

註冊攔截器

前提:springboot啟動類中@ServletComponentScan開啟掃描

package com.blackcat.demo.config;

import com.blackcat.demo.interceptor.FirstIndexInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * <p> 描述 :Web配置
 * @author : blackcat
 * @date : 2020/5/20 14:37
 */
@Slf4j
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    /**
     * <p> 描述 : 註冊攔截器
     * @author : blackcat
     * @date  : 2020/5/20 14:39
     * 在 Spring Boot 中 配置攔截器,只需要實現 WebMvcConfigurer 介面,
     * 在 addInterceptors() 方法中通過 InterceptorRegistry 新增 攔截器 和 匹配路徑 即可。
    */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 註冊攔截器
        InterceptorRegistration first = registry.addInterceptor(new FirstIndexInterceptor());
        // 新增攔截請求
        first.addPathPatterns("/index/**");
        // 新增不攔截的請求
        first.excludePathPatterns("/login");

        // 簡寫格式
//        registry.addInterceptor(new FirstIndexInterceptor()).addPathPatterns("/index/**");
        log.info("註冊攔截器");
    }
}

IndexController

package com.blackcat.demo.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * <p> 描述 :控制器
 * @author : blackcat
 * @date : 2020/5/20 14:27
 */
@Slf4j
@Controller
@RequestMapping("/index")
public class IndexController {

    /**
     * <p> 描述 :
     * @author : blackcat
     * @date  : 2020/5/22 12:57
     *
     * 測試連線:
     * http://localhost:8003/index/get?filter1=123&filter2=456&interceptor1=789
     * 少一個引數就無法返回 ‘ok’
    */
    @RequestMapping(value="/get")
    @ResponseBody
    public String get() {
        return "ok";
    }
}

程式碼結構

因為本文主要說明過濾器攔截器,Servlet跟Listener不多說,可以看註釋。

測試

路徑的攔截是根據@WebFilter註解urlPatterns進行匹配
例如:/index/* 就會攔截 index 下所有方法

啟動專案

專案執行會根據WebConfiguration 註冊攔截器。測試工具postman。

過濾器測試

如果有多個過濾器,一個攔截器通過後,自動進入下一攔截器。必須所有過濾器全通過之後,才會執行攔截器程式碼。

FirstIndexFilter

測試連結:http://localhost:8003/index/get

測試結果:過濾器filter1沒有通過。後續程式碼不執行。

SecondIndexFilter

測試連結:http://localhost:8003/index/get?filter1=123

測試結果:過濾器filter2沒有通過。後續程式碼不執行。(原理同上)

攔截器測試

攔截原理同過濾器相同。

測試連結:http://localhost:8003/index/get?filter1=123&filter2=456

測試結果:攔截器interceptor1未通過。後續程式碼不執行。

測試連結:http://localhost:8003/index/get?filter1=123&filter2=456&interceptor1=789

測試結果:攔截器interceptor1通過。執行訪問方法返回結果。