更新時間:2021-03-26 來源:黑馬程序員 瀏覽量:
springmvc攔截器是我們項目開發中用到的一個功能,常常用于對Handler進行預處理和后處理。本案例來演示一個較簡單的springmvc攔截器的使用,并通過分析源碼來探究攔截器的執行順序是如何控制的。
1.1.1 創建一個maven的war工程
該步驟不再截圖說明
1.1.2 引入maven依賴
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies>
1.2.3 配置web.xml
配置springmvc核心控制器DispatcherServlet,由于需要加載springmvc.xml,所以需要創建一個springmvc.xml文件(文件參考源碼附件)放到classpath下
<!-- 前端控制器(加載classpath:springmvc.xml 服務器啟動創建servlet) --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置初始化參數,創建完DispatcherServlet對象,加載springmvc.xml配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
1.2.1 準備兩個攔截器
兩個攔截器分別命名為MyInterceptor1
、MyInterceptor2
public class MyInterceptor1 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("==1-1====前置攔截器1 執行======"); return true; //ture表示放行 } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("==1-2=====后置攔截器1 執行======"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("==1-3======最終攔截器1 執行======"); } }
public class MyInterceptor2 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("==2-1====前置攔截器2 執行======"); return true; //ture表示放行 } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("==2-2=====后置攔截器2 執行======"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("==2-3======最終攔截器2 執行======"); } }
1.2.2 在springmvc.xml中攔截器
<!--配置攔截器--> <mvc:interceptors> <!--配置攔截器--> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.itheima.interceptor.MyInterceptor1" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.itheima.interceptor.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors>
這兩個攔截器攔截規則相同,并且配置順序
攔截器1
在攔截器2
之前!
1.3.1 準備測試Controller
@Controller public class BizController { @RequestMapping("testBiz") public String showUserInfo(Integer userId, Model model){ System.out.println(">>>>>業務代碼執行-查詢用戶ID為:"+ userId); User user = new User(userId); user.setName("宙斯"); model.addAttribute("userInfo",user); return "user_detail"; } }
該controller會轉發到user_detail.jsp頁面
1.3.2 準備user_detail.jsp
<html> <head> <title>detail</title> </head> <body> 用戶詳情: ${userInfo.id}:${userInfo.name} <%System.out.print(">>>>>jsp頁面的輸出為:");%> <%System.out.println(((User)request.getAttribute("userInfo")).getName());%> </body> </html>
1.3.3 測試效果
啟動項目后,在地址欄訪問/testBiz?userId=1
,然后查看IDE控制臺打印:
==1-1====前置攔截器1 執行====== ==2-1====前置攔截器2 執行====== >>>>>業務代碼執行-查詢用戶ID為:1 ==2-2=====后置攔截器2 執行====== ==1-2=====后置攔截器1 執行====== >>>>>jsp頁面的輸出為:宙斯 ==2-3======最終攔截器2 執行====== ==1-3======最終攔截器1 執行======
通過打印日志發現,攔截器執行順序是:
攔截器1的前置>攔截器2的前置>業務代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
經過測試發現攔截器執行順序如下:
攔截器1的前置>攔截器2的前置>業務代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
我們通過分析源碼來探究下攔截器是如何執行的
當瀏覽器發送/testBiz?userId=1
的請求時,會經過DispatcherServlet
的doDispatch
方法,我們將其取出并觀察其核心代碼(省略非關鍵代碼)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //... try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //1.獲取執行鏈 mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //2.獲取處理器適配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); //... //【3】.執行前置攔截器 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //4.執行業務handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); //【5】.執行后置攔截器 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } //【6】.處理頁面響應,并執行最終攔截器 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } }finally { //... } }
代碼中有關攔截器執行的位置我都添加了注釋,其中注釋中標識的步驟中,3、5、6步驟是攔截器的關鍵步驟
其中,第一步中"獲取執行鏈",執行鏈內容可以通過debug調試查看內容:
可以看到我們自定義的兩個攔截器按順序保存
在doDispatch
方法中,我們添加的注釋的第【3】、【5】、【6】步驟是對攔截器的執行處理,現在分別來查看第【3】、【5】、【6】步驟執行的具體方法的源碼
2.2.1 第【3】步驟
//3.執行前置攔截器中的詳細代碼 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { //獲得本次請求對應的所有攔截器 HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { //按照攔截器順序依次執行每個攔截器的preHandle方法. //并且,interceptorIndex值會一次 + 1 (該值是給后面的最終攔截器使用的) for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors[/color][i][color=black]; //只要每個攔截器不返回false,則繼續執行,否則執行最終攔截器 if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } //最終返回true return true; }
我們可以看到攔截器的preHandler(前置處理)方法是按攔截器(攔截器1、攔截器2)順序執行的,然后我們再來看步驟【5】
2.2.2 第【5】步驟
//5.執行后置攔截器 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { //獲得本次請求對應的所有攔截器 HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { //按倒敘執行每個攔截器的postHandle方法——所以我們看到先執行的攔截器2的postHandle,再執行攔截器1的postHandle for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[/color][color=black]; interceptor.postHandle(request, response, this.handler, mv); } } }
會發現,后置處理
是按照攔截器順序倒敘處理的!
我們最后來看下最終攔截器
2.2.3 第【6】步驟
//執行的方法 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { //... if (mv != null && !mv.wasCleared()) { //處理響應 this.render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else if (this.logger.isDebugEnabled()) { this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling"); } if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (mappedHandler != null) { //6、執行攔截器的最終方法們 mappedHandler.triggerAfterCompletion(request, response, (Exception)null); } } }
其中,有一個render()方法,該方法會直接處理完response。再后則是觸發triggerAfterCompletion方法:
//6、執行攔截器的最終方法 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { //倒敘執行每個攔截器(interceptorIndex為前置攔截器動態計算)的afterCompletion方法 for(int i = this.interceptorIndex; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[/color][/i][color=black][i]; try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable var8) { logger.error("HandlerInterceptor.afterCompletion threw exception", var8); } } } }
由此可以看到,攔截器的最終方法的執行也是按照倒敘來執行的,而且是在響應之后。
攔截器常用于初始化資源,權限監控,會話設置,資源清理等的功能設置,就需要我們對它的執行順序完全掌握,我們通過源碼可以看到,攔截器類似于對我們業務方法的環繞通知效果,并且是通過循環收集好的攔截器集合來控制每個攔截器方法的執行順序,進而可以真正做到深入掌握攔截器的執行機制!
猜你喜歡: