前言
我們在進行 Web 應用開發時,時常需要對請求進行攔截或處理,故 Spring 為我們提供了過濾器和攔截器來應對這種情況。那麼兩者之間有什麼不同呢?本文將詳細講解兩者的區別和對應的使用場景。
(本文的程式碼實現首先是基於 SpringBoot,Spring 的實現方式僅簡單描述)
1. 過濾器
1.1. 什麼是過濾器
過濾器(Filter),是 Servlet 規範規定的,在 Servlet 前執行的。用於攔截和處理 HTTP 請求和響應,可用於身份認證、授權、日誌記錄和設定字符集(CharacterEncodingFilter)等場景。
過濾器位於整個請求處理流程的最前端,因此在請求到達 Controller 層前,都會先被過濾器處理。
過濾器可以攔截多個請求或響應,一個請求或響應也可以被多個過濾器攔截。
1.2. 如何建立過濾器
Filter 的生命週期對應的三個關鍵方法:
方法 | 說明 |
---|---|
init() | 當請求發起時,會呼叫 init() 方法初始化 Filter 例項,僅初始化一次。若需要設定初始化引數的時可呼叫該方法。 |
doFilter() | 攔截要執行的請求,對請求和響應進行處理。 |
destroy() | 請求結束時呼叫該方法銷燬 Filter 的例項。 |
下面將介紹二種方法建立 Filter。
1.2.1 實現 Filter 介面
1.建立 Filter 處理類,實現javax.servlet.Filter介面,加上@WebFilter註解配置攔截 Url,但是不能指定過濾器執行順序,也可透過web.xml配置。
@WebFilter(urlPatterns = "/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 用於完成 Filter 的初始化 Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("過濾器已經攔截成功!!!"); // 執行該方法之前,即對使用者請求進行預處理;執行該方法之後,即對伺服器響應進行後處理。 chain.doFilter(request,response); } @Override public void destroy() { // 用於 Filter 銷燬前,完成某些資源的回收; Filter.super.destroy(); } }
2.在啟動類新增註解@ServletComponentScan ,讓 Spring 可以掃描到。
@WebFilter(urlPatterns = "/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 用於完成 Filter 的初始化 Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("過濾器已經攔截成功!!!"); // 執行該方法之前,即對使用者請求進行預處理;執行該方法之後,即對伺服器響應進行後處理。 chain.doFilter(request,response); } @Override public void destroy() { // 用於 Filter 銷燬前,完成某些資源的回收; Filter.super.destroy(); } }
3.建立 Controller 發起 Url 請求。
@RestController public class MyFilterController { @GetMapping("/testFilter") public String testFilter(){ return "Hello World"; } }
攔截結果
1.2.2. 透過@Component 註解
1.建立 Filter 處理類,實現javax.servlet.Filter介面,加@Component註解。
可以使用@Order註解保證過濾器執行順序,不加則按照類名排序。
過濾器不能指定攔截的url , 只能預設攔截全部。
@Component @Order(1) public class MyComponentFilter1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("我是過濾器1已經攔截成功!!!"); chain.doFilter(request,response); } @Override public void destroy() { Filter.super.destroy(); } }
@Component @Order(2) public class MyComponentFilter2 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException System.out.println("我是過濾器2已經攔截成功!!!"); chain.doFilter(request,response); } @Override public void destroy() { Filter.super.destroy(); } }
2-3 步驟同 1.2.1,結果如下。
2. 攔截器
2.1. 什麼是攔截器
攔截器(Interceptor),和Servlet無關,由Spring框架實現。可用於身份認證、授權、日誌記錄、預先設定資料以及統計方法的執行效率等。
一般基於 Java 的反射機制實現,屬於AOP的一種運用。
目前瞭解的 Spring 中的攔截器有:
HandlerInterceptor
MethodInterceptor
2.2. HandlerInterceptor 攔截器
2.2.1簡介
HandlerInterceptor 類似 Filter,攔截的是請求地址 ,但提供更精細的的控制能力,這裏注意下必須過DispatcherServlet 的請求才會被攔截。
它允許你在請求處理前、處理後以及檢視渲染完成前執行自定義邏輯,可以用來對請求地址做一些認證授權、預處理,也可以計算一個請求的響應時間等,還可以處理跨域(CORS)問題。
簡單的執行流程描述:
請求到達 DispatcherServlet,然後傳送至 Interceptor,執行 preHandler;
請求到達 Controller,請求結束後,執行 postHandler。
2.2.2如何實現
建立 Interceptor 類,實現HandlerInterceptor介面,重寫 3 個方法,加@Component註解。
@Component public class MyHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //請求開始時間 long startTime = System.currentTimeMillis(); request.setAttribute("startTime", startTime); System.out.println("startTime : " + new Date(startTime)); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { long startTime = (Long)request.getAttribute("startTime"); long endTime = System.currentTimeMillis(); // 統計耗時 long executeTime = endTime - startTime; System.out.println("executeTime : " + executeTime + "ms"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
2.配置攔截器,實現WebMvcConfigurer介面,加@Configuration註解並重寫addInterceptors方法。
@Configuration public class MyWebConfigurer implements WebMvcConfigurer { @Resource private MyHandlerInterceptor myHandlerInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { List<String> patterns = new ArrayList<>(); patterns.add("/test/handlerInterceptor"); registry.addInterceptor(myHandlerInterceptor) .addPathPatterns(patterns) // 需要攔截的請求 .excludePathPatterns(); // 不需要攔截的請求 } }
攔截結果如下:
Spring 專案如何實現?
可透過使用mvc:interceptors標籤來宣告需要加入到 SpringMVC 攔截器鏈中的攔截器。
2.3. MethodInterceptor 攔截器
2.3.1. 簡介
MethodInterceptor 是 AOP 中的攔截器,它攔截的目標是方法,可以不是 Controller 中的方法。
在對一些普通的方法上的攔截可以使用該攔截器,這是 HandlerInterceptor 無法實現的。
可用來進行方法級別的身份認證、授權以及日誌記錄等,也可基於自定義註解實現一些通用的方法增強功能。
2.3.2. 如何實現
MethodInterceptor 是基於 AOP 實現的,所以根據不同的代理有多種實現方式,更多的實現方式和原理我將在整理 Spring AOP 的時候詳細接受。
這裏我將介紹透過BeanNameAutoProxyCreator自動代理實現攔截。該類是基於 Bean 名稱的自動代理,可以針對特定的Bean進行個性化的 AOP 配置。
1.建立簡單的需要攔截的方法。
public interface UserService { public String getUser(); }
2.建立 Interceptor 類,實現MethodInterceptor介面,重寫invoke方法,加@Component註解。
@Component public class UserServiceImpl implements UserService{ @Override public String getUser() { return "我是福星"; } }
3.配置自動代理,加@Configuration註解並建立自動代理BeanNameAutoProxyCreator。
@Configuration public class MyMethodConfigurer { @Resource private MyMethodInterceptor myMethodInterceptor; @Bean public BeanNameAutoProxyCreator beanNameAutoProxyCreator() { // 使用BeanNameAutoProxyCreator來建立代理 BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); // 指定一組需要自動代理的Bean名稱,Bean名稱可以使用*萬用字元 beanNameAutoProxyCreator.setBeanNames("user*"); //設定攔截器名稱,這些攔截器是有先後順序的 beanNameAutoProxyCreator.setInterceptorNames("myMethodInterceptor"); return beanNameAutoProxyCreator; } }
發起請求後,呼叫該方法前會進行攔截。
3. 總結
過濾器一般用於對 Servlet 請求和響應進行通用性的處理,通常關注請求和響應內容,而不涉及具體的業務邏輯。而攔截器用於對 SpringMVC 的請求和響應進行特定的業務處理,通常與控制器層的請求處理有關。
不論是過濾器和攔截器,都可以有多個。執行順序上攔截器是由配置中的順序決定,而過濾器可透過@Component+@Order決定,也可由web.xml檔案中的配置順序決定。
總的來說,攔截器的使用更加靈活,Filter 能做的事情,攔截器也能做。Filter 一般用於對 URL 請求做編碼處理、過濾無用引數、安全校驗(比如登陸態校驗),如果涉及業務邏輯上的,還是建議用攔截器。