更新時間:2021-03-26 來源:黑馬程序員 瀏覽量:
springmvc攔截器是我們項目開發(fā)中用到的一個功能,常常用于對Handler進行預(yù)處理和后處理。本案例來演示一個較簡單的springmvc攔截器的使用,并通過分析源碼來探究攔截器的執(zhí)行順序是如何控制的。
1.1.1 創(chuàng)建一個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,所以需要創(chuàng)建一個springmvc.xml文件(文件參考源碼附件)放到classpath下
<!-- 前端控制器(加載classpath:springmvc.xml 服務(wù)器啟動創(chuàng)建servlet) --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置初始化參數(shù),創(chuàng)建完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 執(zhí)行======"); return true; //ture表示放行 } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("==1-2=====后置攔截器1 執(zhí)行======"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("==1-3======最終攔截器1 執(zhí)行======"); } }
public class MyInterceptor2 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("==2-1====前置攔截器2 執(zhí)行======"); return true; //ture表示放行 } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("==2-2=====后置攔截器2 執(zhí)行======"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("==2-3======最終攔截器2 執(zhí)行======"); } }
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>
這兩個攔截器攔截規(guī)則相同,并且配置順序
攔截器1
在攔截器2
之前!
1.3.1 準備測試Controller
@Controller public class BizController { @RequestMapping("testBiz") public String showUserInfo(Integer userId, Model model){ System.out.println(">>>>>業(yè)務(wù)代碼執(zhí)行-查詢用戶ID為:"+ userId); User user = new User(userId); user.setName("宙斯"); model.addAttribute("userInfo",user); return "user_detail"; } }
該controller會轉(zhuǎn)發(fā)到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 執(zhí)行====== ==2-1====前置攔截器2 執(zhí)行====== >>>>>業(yè)務(wù)代碼執(zhí)行-查詢用戶ID為:1 ==2-2=====后置攔截器2 執(zhí)行====== ==1-2=====后置攔截器1 執(zhí)行====== >>>>>jsp頁面的輸出為:宙斯 ==2-3======最終攔截器2 執(zhí)行====== ==1-3======最終攔截器1 執(zhí)行======
通過打印日志發(fā)現(xiàn),攔截器執(zhí)行順序是:
攔截器1的前置>攔截器2的前置>業(yè)務(wù)代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
經(jīng)過測試發(fā)現(xiàn)攔截器執(zhí)行順序如下:
攔截器1的前置>攔截器2的前置>業(yè)務(wù)代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
我們通過分析源碼來探究下攔截器是如何執(zhí)行的
當瀏覽器發(fā)送/testBiz?userId=1
的請求時,會經(jīng)過DispatcherServlet
的doDispatch
方法,我們將其取出并觀察其核心代碼(省略非關(guān)鍵代碼)
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.獲取執(zhí)行鏈 mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //2.獲取處理器適配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); //... //【3】.執(zhí)行前置攔截器 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //4.執(zhí)行業(yè)務(wù)handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); //【5】.執(zhí)行后置攔截器 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } //【6】.處理頁面響應(yīng),并執(zhí)行最終攔截器 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 { //... } }
代碼中有關(guān)攔截器執(zhí)行的位置我都添加了注釋,其中注釋中標識的步驟中,3、5、6步驟是攔截器的關(guān)鍵步驟
其中,第一步中"獲取執(zhí)行鏈",執(zhí)行鏈內(nèi)容可以通過debug調(diào)試查看內(nèi)容:
可以看到我們自定義的兩個攔截器按順序保存
在doDispatch
方法中,我們添加的注釋的第【3】、【5】、【6】步驟是對攔截器的執(zhí)行處理,現(xiàn)在分別來查看第【3】、【5】、【6】步驟執(zhí)行的具體方法的源碼
2.2.1 第【3】步驟
//3.執(zhí)行前置攔截器中的詳細代碼 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { //獲得本次請求對應(yīng)的所有攔截器 HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { //按照攔截器順序依次執(zhí)行每個攔截器的preHandle方法. //并且,interceptorIndex值會一次 + 1 (該值是給后面的最終攔截器使用的) for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors[/color][i][color=black]; //只要每個攔截器不返回false,則繼續(xù)執(zhí)行,否則執(zhí)行最終攔截器 if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } //最終返回true return true; }
我們可以看到攔截器的preHandler(前置處理)方法是按攔截器(攔截器1、攔截器2)順序執(zhí)行的,然后我們再來看步驟【5】
2.2.2 第【5】步驟
//5.執(zhí)行后置攔截器 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { //獲得本次請求對應(yīng)的所有攔截器 HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { //按倒敘執(zhí)行每個攔截器的postHandle方法——所以我們看到先執(zhí)行的攔截器2的postHandle,再執(zhí)行攔截器1的postHandle for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[/color][color=black]; interceptor.postHandle(request, response, this.handler, mv); } } }
會發(fā)現(xiàn),后置處理
是按照攔截器順序倒敘處理的!
我們最后來看下最終攔截器
2.2.3 第【6】步驟
//執(zhí)行的方法 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { //... if (mv != null && !mv.wasCleared()) { //處理響應(yīng) 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、執(zhí)行攔截器的最終方法們 mappedHandler.triggerAfterCompletion(request, response, (Exception)null); } } }
其中,有一個render()方法,該方法會直接處理完response。再后則是觸發(fā)triggerAfterCompletion方法:
//6、執(zhí)行攔截器的最終方法 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { //倒敘執(zhí)行每個攔截器(interceptorIndex為前置攔截器動態(tài)計算)的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); } } } }
由此可以看到,攔截器的最終方法的執(zhí)行也是按照倒敘來執(zhí)行的,而且是在響應(yīng)之后。
攔截器常用于初始化資源,權(quán)限監(jiān)控,會話設(shè)置,資源清理等的功能設(shè)置,就需要我們對它的執(zhí)行順序完全掌握,我們通過源碼可以看到,攔截器類似于對我們業(yè)務(wù)方法的環(huán)繞通知效果,并且是通過循環(huán)收集好的攔截器集合來控制每個攔截器方法的執(zhí)行順序,進而可以真正做到深入掌握攔截器的執(zhí)行機制!
猜你喜歡: