SpringMVC系列 教程(六):攔截器詳解(附源碼分析)


來源:https://www.cnblogs.com/
fangjian0423/p/springMVC-interceptor.html
一.?前言SpringMVC是目前主流的Web MVC框架之一。?
攔截器是每個Web框架必備的功能,也是個老生常談的主題了。
本文將分析SpringMVC的攔截器功能是如何設(shè)計的,讓讀者了解該功能設(shè)計的原理。
二.?重要接口及類介紹1.?HandlerExecutionChain類
由HandlerMethod和Interceptor集合組成的類,會被HandlerMapping接口的getHandler方法獲取。

2.?HandlerInterceptor接口

SpringMVC攔截器基礎(chǔ)接口。
3. AbstractHandlerMapping
HandlerMapping的基礎(chǔ)抽象類。

4.?AsyncHandlerInterceptor
繼承HandlerInterceptor的接口,額外提供了afterConcurrentHandlingStarted方法,該方法是用來處理異步請求。當(dāng)Controller中有異步請求方法的時候會觸發(fā)該方法。樓主做過測試,異步請求先支持preHandle、然后執(zhí)行afterConcurrentHandlingStarted。異步線程完成之后執(zhí)行preHandle、postHandle、afterCompletion。有興趣的讀者可自行研究。
5.?HandlerInterceptorAdapter
? 實現(xiàn)AsyncHandlerInterceptor接口的抽象類,一般我們使用攔截器的話都會繼承這個類。然后復(fù)寫相應(yīng)的方法。
6. WebRequestInterceptor
與HandlerInterceptor接口類似,區(qū)別是WebRequestInterceptor的preHandle沒有返回值。還有WebRequestInterceptor是針對請求的,接口方法參數(shù)中沒有response。

AbstractHandlerMapping內(nèi)部的interceptors是個Object類型集合。處理的時候判斷為MappedInterceptor[加入到mappedInterceptors集合中];HandlerInterceptor、WebRequestInterceptor(適配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]
7. MappedInterceptor
一個包括includePatterns和excludePatterns字符串集合并帶有HandlerInterceptor的類。很明顯,就是對于某些地址做特殊包括和排除的攔截器。
? 
8. ConversionServiceExposingInterceptor
默認(rèn)的
首先我們看下攔截器的如何被調(diào)用的。
Web請求被DispatcherServlet截獲后,會調(diào)用DispatcherServlet的doDispatcher方法。


很明顯地看到,在HandlerAdapter處理之后,以及處理完成之后會調(diào)用HandlerExecutionChain的方法。
HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion方法如下:



很明顯,就是調(diào)用內(nèi)部實現(xiàn)HandlerInterceptor該接口集合的各個對應(yīng)方法。
?
下面我們看下HandlerExecutionChain的構(gòu)造過程。
?HandlerExecutionChain是從HandlerMapping接口的getHandler方法獲取的。
?HandlerMapping的基礎(chǔ)抽象類AbstractHandlerMapping中:


我們看到,HandlerExecutionChain的攔截器是從AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors屬性中獲取的。
四.?攔截器的配置清楚了HandlerExecutionChain的攔截器屬性如何構(gòu)造之后,下面來看下SpringMVC是如何配置攔截器的。
1. *-dispatcher.xml配置文件中添加?
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><mvc:exclude-mapping path="/login"/><mvc:exclude-mapping path="/index"/><bean class="package.interceptor.XXInterceptor"/>mvc:interceptor>mvc:interceptors>
這里配置的每個
其中子標(biāo)簽

2. 配置RequestMappingHandlerMapping,并配置該bean對應(yīng)的interceptors集合屬性。這里的interceptors集合是個Object類型的泛型集合。
AbstractHandlerMapping抽象類只暴露了1個攔截器的set方法 -> interceptors。
adaptedInterceptors和mappedInterceptors均沒有暴露set方法,因此我們只能為RequestMappingHandlerMapping配置interceptors屬性。
其實AbstractHandlerMapping內(nèi)部的initInterceptors方法中,會遍歷interceptors集合,然后判斷各個項是否是MappedInterceptor、HandlerInterceptor、WebRequestInterceptor。
其中MappedInterceptor類型的攔截器會被加到mappedInterceptors集合中,HandlerInterceptor類型的會被加到adaptedInterceptors集合中,WebRequestInterceptor類型的會被適配成WebRequestHandlerInterceptorAdapter加到adaptedInterceptors集合中。


如果讀者配置了:
那么配置如下:
class ="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"><property name="interceptors"><bean class="package.interceptor.XXInterceptor"/>property><property name="order" value="-1"/>bean>
否則,可以去掉order這個屬性的設(shè)置。
一般建議使用第一種方法。
五. 編寫自定義的攔截器public class LoginInterceptor extends HandlerInterceptorAdapter {public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {// 獲得請求路徑的uriString uri = request.getRequestURI();// 判斷路徑是登出還是登錄驗證,是這兩者之一的話執(zhí)行Controller中定義的方法if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {return true;}// 進入登錄頁面,判斷session中是否有key,有的話重定向到首頁,否則進入登錄界面if(uri.endsWith("/login/") || uri.endsWith("/login")) {if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {response.sendRedirect(request.getContextPath() + "/index");} else {return true;}}// 其他情況判斷session中是否有key,有的話繼續(xù)用戶的操作if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {return true;}// 最后的情況就是進入登錄頁面response.sendRedirect(request.getContextPath() + "/login");return false;}}
登錄Controller:
public class LoginController {public String index() {return "login";}public String auth( String username, HttpServletRequest req) {req.getSession().setAttribute("loginUser", username);return "redirect:/index";}public String out(HttpServletRequest req) {req.getSession().removeAttribute("loginUser");return "redirect:/login";}}
?*-diapatcher.xml配置:
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="org.format.demo.interceptor.LoginInterceptor"/>mvc:interceptor>mvc:interceptors>
PS:我們看到LoginInterceptor里的preHandle方法對于地址“/login/auth”和"/login/out"不處理。
因此,可以寫點配置,少寫帶java代碼。在攔截器配置中添加2個exclude-mapping,并且去掉LoginInterceptor里的
if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {return true;}
配置新增:
<mvc:exclude-mapping path="/login/out"/><mvc:exclude-mapping path="/login/auth"/>
六. 小結(jié)
總結(jié)了SpringMVC攔截器的原理以及各種配置,像網(wǎng)上很多人會問為什么攔截器執(zhí)行preHandle方法返回false之后還是會執(zhí)行afterCompletion方法,其實我們看下源碼就知道了。
關(guān)于異步請求方面的攔截器以及第二種配置方法(interceptors集合屬性可加入繼承自HandlerInterceptorAdapter抽象類的類以及實現(xiàn)WebRequestInterceptor接口的類),讀者可自行研究。
文中難免有錯誤,希望讀者能夠指出來。
- End -

技術(shù)連載目錄(可點擊跳轉(zhuǎn)即可閱讀):
面試題系列教程??點擊-->?面試題技術(shù)干貨連載目錄?跳轉(zhuǎn)
Maven系列教程??點擊-->?Maven技術(shù)干貨連載目錄?跳轉(zhuǎn)
MyBatis系列教程??點擊-->?MyBatis技術(shù)干貨連載目錄?跳轉(zhuǎn)
JVM調(diào)優(yōu)總結(jié)系列教程??點擊-->?JVM調(diào)優(yōu)技術(shù)干貨連載目錄?跳轉(zhuǎn)
點擊在看,愿你我不再陌生?![]()
