攔截器太強大了,吃透它(SpringMVC系列)
文末可領(lǐng)取最近剛整理的,后端必備的 200 本書籍。
1、本文內(nèi)容
回顧下 springmvc 處理請求的過程 如何干預(yù) springmvc 的處理流程? 加入攔截器后 springmvc 的處理過程 攔截器的用法(具體 2 個步驟) 多個攔截器的執(zhí)行順序 通過案例驗證攔截器的執(zhí)行順序 一起讀源碼 領(lǐng)取后端必備的 200 本書籍
2、回顧下 springmvc 處理請求的過程
簡化下過程,如下圖,過程還是非常簡單的

3、如何干預(yù) springmvc 的處理流程?
比如我們的系統(tǒng)中,除了登錄的方法,其他所有方法都需要先驗證一下用戶是否登錄了,若未登錄,讓用戶先跳轉(zhuǎn)到登錄頁面,最笨的方法是在所有需要驗證的方法內(nèi)部都加上驗證的代碼,那么有沒有更好的方法呢?
如下圖,如果我們將驗證登錄的代碼放在調(diào)用自定義controller的方法之前,是不是就特別爽了,就不用在所有代碼中都添加驗證代碼了:

springmvc 確實為我們考慮到了這種需求,springmvc 在處理流程中為我們提供了 3 個擴展點可以對整個處理流程進行干預(yù),這個就是 springmvc 中攔截器提供的功能,下面咱們來看一下攔截器怎么玩的。
4、攔截器(HandlerInterceptor)
springmvc 中使用org.springframework.web.servlet.HandlerInterceptor接口來表示攔截器,如下,提供了 3 個默認(rèn)方法。
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
下面來解釋下這 3 個方法。
preHandle 方法
在調(diào)用自定義的 controller 之前會調(diào)用這個方法,若返回 false,將跳過 controller 方法的調(diào)用,否則將進入到 controller 的方法中。
postHandle 方法
調(diào)用自定義 controller 中的方法之后會調(diào)用這個方法,此時還沒有渲染視圖,也就是還沒有將結(jié)果輸出到客戶端
afterCompletion 方法
整個請求處理完畢之后,即結(jié)果以及輸出到客戶端之后,調(diào)用這個方法,此時可以做一些清理的工作,注意這個方法最后一個參數(shù)是 Exception 類型的,說明這個方法不管整個過程是否有異常,這個方法都會被調(diào)用。
其他說明
3 個方法中的 hander 參數(shù)表示處理器,通常就是我們自定義的 controller
5、加入攔截器后 springmvc 的處理流程
加入 springmvc 之后處理流程如下圖,注意黃色背景的幾個對應(yīng)攔截器的 3 個方法。

6、攔截器的用法(2 步驟)
step1:定義攔截器
自定義一個類,需要實現(xiàn)
org.springframework.web.servlet.HandlerInterceptor接口,如下,然后實現(xiàn)具體的方法
public class HandlerInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
}
}
step2:將自定義的攔截器添加到 springmvc 配置文件中
配置如下,需要將自定義的攔截器添加到 springmvc 配置文件中
可以同時配置多個攔截器,每個攔截器通過<mvc:interceptor>標(biāo)簽來定義,多個攔截器之間可以指定順序,順序和<mvc:interceptor>定義的順序一致 每個攔截器需要指定實現(xiàn)類、攔截的 url、排除的 url
<!-- interceptors用來定義攔截器,其內(nèi)部可以定義多個攔截器 -->
<mvc:interceptors>
<!-- mvc:interceptor 標(biāo)簽用來定義一個攔截器 -->
<mvc:interceptor>
<!-- 用來指定攔截器匹配的url,比如/user/**會攔截所有以/user開頭的url -->
<mvc:mapping path="/user/**"/>
<!-- 用來指定攔截器排除的url,即這些url不會被攔截器攔截 -->
<mvc:exclude-mapping path="/user/login"/>
<!-- 用來指定攔截器 -->
<bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor1"/>
</mvc:interceptor>
<!-- 其他攔截器配置信息 -->
<mvc:interceptor>
.....
</mvc:interceptor>
</mvc:interceptors>
7、多個攔截器時如何執(zhí)行?
當(dāng)請求的 url 匹配到多個攔截器的時候,執(zhí)行順序如下圖
preHandler 方法是順序執(zhí)行的,即和定義的順序是一致的 而攔截器中的其他 2 個方法 postHandler、afterCompletion 是逆序執(zhí)行的,和 pewHandler 的順序相反

8、案例驗證攔截器的執(zhí)行順序
下面通過案例結(jié)合 3 個場景來看一下攔截器的執(zhí)行順序,加深大家的理解。
準(zhǔn)備代碼
UserController
package com.javacode2018.springmvc.chat09.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login() {
return "login view";
}
@RequestMapping("/add")
public String add() {
return "add view";
}
@RequestMapping("/del")
public String modify() {
return "modify view";
}
@RequestMapping("/list")
public String list() {
return "list view";
}
}
創(chuàng)建 2 個攔截器
攔截器 1:HandlerInterceptor1
package com.javacode2018.springmvc.chat09.intercetor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HandlerInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
}
}
攔截器 2:HandlerInterceptor2
package com.javacode2018.springmvc.chat09.intercetor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HandlerInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
}
}
配置文件中配置攔截器
下面將 2 個攔截器加入 springmvc 的配置文件中,都會攔截/user 開頭的所有請求,/user/login 被排除在外
<!-- interceptors用來定義攔截器,其內(nèi)部可以定義多個攔截器 -->
<mvc:interceptors>
<!-- mvc:interceptor 標(biāo)簽用來定義一個攔截器 -->
<mvc:interceptor>
<!-- 用來指定攔截器匹配的url -->
<mvc:mapping path="/user/**"/>
<!-- 用來指定攔截器排除的url,即這些url不會被攔截器攔截 -->
<mvc:exclude-mapping path="/user/login"/>
<!-- 用來指定攔截器 -->
<bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor1"/>
</mvc:interceptor>
<!-- mvc:interceptor 標(biāo)簽用來定義一個攔截器 -->
<mvc:interceptor>
<!-- 用來指定攔截器匹配的url -->
<mvc:mapping path="/user/**"/>
<!-- 用來指定攔截器排除的url,即這些url不會被攔截器攔截 -->
<mvc:exclude-mapping path="/user/login"/>
<!-- 用來指定攔截器 -->
<bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
場景 1
按照下列表格,調(diào)整下 2 個攔截器的 preHandle 方法返回值
| 攔截器 | preHandle 方法返回值 |
|---|---|
| HandlerInterceptor1 | true |
| HandlerInterceptor2 | true |
訪問http://localhost:8080/chat09/user/add,輸出
HandlerInterceptor1.preHandle
HandlerInterceptor2.preHandle
HandlerInterceptor2.postHandle
HandlerInterceptor1.postHandle
HandlerInterceptor2.afterCompletion
HandlerInterceptor1.afterCompletion
場景 2
按照下列表格,調(diào)整下 2 個攔截器的 preHandle 方法返回值
| 攔截器 | preHandle 方法返回值 |
|---|---|
| HandlerInterceptor1 | false |
| HandlerInterceptor2 | true |
訪問http://localhost:8080/chat09/user/add,輸出
HandlerInterceptor1.preHandle
場景 3
按照下列表格,調(diào)整下 2 個攔截器的 preHandle 方法返回值
| 攔截器 | preHandle 方法返回值 |
|---|---|
| HandlerInterceptor1 | true |
| HandlerInterceptor2 | false |
訪問http://localhost:8080/chat09/user/add,輸出
HandlerInterceptor1.preHandle
HandlerInterceptor2.preHandle
HandlerInterceptor1.afterCompletion
9、源碼解析
攔截器的執(zhí)行過程主要位于下面這段代碼中
代碼位置:org.springframework.web.servlet.DispatcherServlet#doDispatch
如下代碼,咱們將關(guān)鍵代碼提取出來,大家注意看注釋,解釋了每個方法內(nèi)部干的事情,具體每個方法的內(nèi)部,咱們就不進去了,很簡單,有興趣的可以自己去看,這里給大家提點建議:看源碼的時候,先站在高的層次上面看代碼,了解大的功能及流程之后,再去細看某個功能點,要避免上來就陷入細節(jié)中,容易迷失方向。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
Exception dispatchException = null;
try {
//①、根據(jù)請求找到處理器
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//②、內(nèi)部會調(diào)用攔截器的preHandler方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//③、調(diào)用實際的處理器(即這里面會調(diào)用咱們controller中的方法)
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//④、調(diào)用攔截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
//⑤、渲染視圖 & 調(diào)用攔截器的afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
//⑥:異常情況,調(diào)用攔截器的afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
}
10、案例代碼
git地址:https://gitee.com/javacode2018/springmvc-series

11、SpringMVC 系列
SpringMVC 系列第 1 篇:helloword SpringMVC 系列第 2 篇:@Controller、@RequestMapping SpringMVC 系列第 3 篇:異常高效的一款接口測試?yán)?/a> SpringMVC 系列第 4 篇:controller 常見的接收參數(shù)的方式 SpringMVC 系列第 5 篇:@RequestBody 大解密,說點你不知道的 SpringMVC 系列第 6 篇:上傳文件的 4 種方式,你都會么? SpringMVC 系列第 7 篇:SpringMVC 返回視圖常見的 5 種方式,你會幾種? SpringMVC 系列第 8 篇:返回 json & 通用返回值設(shè)計 SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思? SpringMVC 系列第 10 篇:異步處理太強大了,系統(tǒng)性能大幅提升 SpringMVC 系列第 11 篇:集成靜態(tài)資源
12、更多好文章
Spring 高手系列(共 56 篇) Java 高并發(fā)系列(共 34 篇) MySql 高手系列(共 27 篇) Maven 高手系列(共 10 篇) Mybatis 系列(共 12 篇) 聊聊 db 和緩存一致性常見的實現(xiàn)方式 接口冪等性這么重要,它是什么?怎么實現(xiàn)? 泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!
13、領(lǐng)取后端必備的 200 本書
這里幫大家整理 200 本后端必備的書籍,掃描下面二維碼即可看到書籍列表,直接領(lǐng)取電子版,也可以點擊【閱讀原文】領(lǐng)取
