SpringBoot 過濾器,攔截器初步學習整理(有示例程式碼)
引言
關於兩者的理論知識,網上有太多就補貼在本章了。該文章主要以程式碼的形式說明,方便新手理解。
這裡也是新手學習時整理的文件,主要針對於新手的,如果有不正確的地方希望加一指正。
兩者的區別
- Filter 是基於 函式回撥的,而 Interceptor 則是基於 Java反射 和 動態代理。
- Filter 依賴於 Servlet 容器,而 Interceptor 它依賴於web框架。
- Filter 對幾乎 所有的請求 起作用,而 Interceptor 只對 Controller 對請求起作用。
執行順序
對於自定義 Servlet 對請求分發流程:
- Filter 過濾請求處理;
- Servlet 處理請求;
- Filter 過濾響應處理。
對於自定義 Controller 的請求分發流程:
- Filter 過濾請求處理:
- Interceptor 攔截請求處理;
- 對應的 HandlerAdapter 處理請求;
- Interceptor 攔截響應處理;
- Interceptor 的最終處理;
- 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通過。執行訪問方法返回結果。