更新時間:2021-03-26 來源:黑馬程序員 瀏覽量:
springmvc攔截器是我們項(xiàng)目開發(fā)中用到的一個功能,常常用于對Handler進(jìn)行預(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 準(zhǔn)備兩個攔截器
兩個攔截器分別命名為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 準(zhǔn)備測試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 準(zhǔn)備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 測試效果
啟動項(xiàng)目后,在地址欄訪問/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í)行的
當(dāng)瀏覽器發(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í)行的位置我都添加了注釋,其中注釋中標(biāo)識的步驟中,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í)行前置攔截器中的詳細(xì)代碼
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í)行順序,進(jìn)而可以真正做到深入掌握攔截器的執(zhí)行機(jī)制!
猜你喜歡: