javascript
面试官:给我说一下 Spring MVC 拦截器的原理?
前言
SpringMVC是目前主流的Web MVC框架之一。
攔截器是每個(gè)Web框架必備的功能,也是個(gè)老生常談的主題了。
本文將分析SpringMVC的攔截器功能是如何設(shè)計(jì)的,讓讀者了解該功能設(shè)計(jì)的原理。
重要接口及類介紹
1. HandlerExecutionChain類
由HandlerMethod和Interceptor集合組成的類,會(huì)被HandlerMapping接口的getHandler方法獲取。
2. HandlerInterceptor接口
SpringMVC攔截器基礎(chǔ)接口。
3. AbstractHandlerMapping
HandlerMapping的基礎(chǔ)抽象類。
4. AsyncHandlerInterceptor
繼承HandlerInterceptor的接口,額外提供了afterConcurrentHandlingStarted方法,該方法是用來處理異步請(qǐng)求。當(dāng)Controller中有異步請(qǐng)求方法的時(shí)候會(huì)觸發(fā)該方法。樓主做過測試,異步請(qǐng)求先支持preHandle、然后執(zhí)行afterConcurrentHandlingStarted。異步線程完成之后執(zhí)行preHandle、postHandle、afterCompletion。有興趣的讀者可自行研究。
5. HandlerInterceptorAdapter
實(shí)現(xiàn)AsyncHandlerInterceptor接口的抽象類,一般我們使用攔截器的話都會(huì)繼承這個(gè)類。然后復(fù)寫相應(yīng)的方法。
6. WebRequestInterceptor
與HandlerInterceptor接口類似,區(qū)別是WebRequestInterceptor的preHandle沒有返回值。還有WebRequestInterceptor是針對(duì)請(qǐng)求的,接口方法參數(shù)中沒有response。
AbstractHandlerMapping內(nèi)部的interceptors是個(gè)Object類型集合。處理的時(shí)候判斷為MappedInterceptor[加入到mappedInterceptors集合中];HandlerInterceptor、WebRequestInterceptor(適配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]
7. MappedInterceptor
一個(gè)包括includePatterns和excludePatterns字符串集合并帶有HandlerInterceptor的類。很明顯,就是對(duì)于某些地址做特殊包括和排除的攔截器。
8. ConversionServiceExposingInterceptor
默認(rèn)的標(biāo)簽初始化的時(shí)候會(huì)初始化ConversionServiceExposingInterceptor這個(gè)攔截器,并被當(dāng)做構(gòu)造方法的參數(shù)來構(gòu)造MappedInterceptor。之后會(huì)被加入到AbstractHandlerMapping的mappedInterceptors集合中。該攔截器會(huì)在每個(gè)請(qǐng)求之前往request中丟入ConversionService。主要用于spring:eval標(biāo)簽的使用。
源碼分析
首先我們看下攔截器的如何被調(diào)用的。
Web請(qǐng)求被DispatcherServlet截獲后,會(huì)調(diào)用DispatcherServlet的doDispatcher方法。
很明顯地看到,在HandlerAdapter處理之后,以及處理完成之后會(huì)調(diào)用HandlerExecutionChain的方法。
HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion方法如下:
很明顯,就是調(diào)用內(nèi)部實(shí)現(xiàn)HandlerInterceptor該接口集合的各個(gè)對(duì)應(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: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>這里配置的每個(gè)mvc:interceptor都會(huì)被解析成MappedInterceptor。
其中子標(biāo)簽<mvc:mapping path="/"/>會(huì)被解析成MappedInterceptor的includePatterns屬性;<mvc:exclude-mapping path="/"/>會(huì)被解析成MappedInterceptor的excludePatterns屬性;會(huì)被解析成MappedInterceptor的interceptor屬性。
mvc:interceptors這個(gè)標(biāo)簽是被InterceptorsBeanDefinitionParser類解析。
\2. 配置RequestMappingHandlerMapping,并配置該bean對(duì)應(yīng)的interceptors集合屬性。這里的interceptors集合是個(gè)Object類型的泛型集合。
AbstractHandlerMapping抽象類只暴露了1個(gè)攔截器的set方法 -> interceptors。
adaptedInterceptors和mappedInterceptors均沒有暴露set方法,因此我們只能為RequestMappingHandlerMapping配置interceptors屬性。
其實(shí)AbstractHandlerMapping內(nèi)部的initInterceptors方法中,會(huì)遍歷interceptors集合,然后判斷各個(gè)項(xiàng)是否是MappedInterceptor、HandlerInterceptor、WebRequestInterceptor。
其中MappedInterceptor類型的攔截器會(huì)被加到mappedInterceptors集合中,HandlerInterceptor類型的會(huì)被加到adaptedInterceptors集合中,WebRequestInterceptor類型的會(huì)被適配成WebRequestHandlerInterceptorAdapter加到adaptedInterceptors集合中。
如果讀者配置了:
<mvc:annotation-driven/>那么配置如下:
<bean?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這個(gè)屬性的設(shè)置。
為什么呢?請(qǐng)參考樓主的另外一篇博客:http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html
一般建議使用第一種方法。
編寫自定義的攔截器
public?class?LoginInterceptor?extends?HandlerInterceptorAdapter?{@Overridepublic?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,Object?handler)?throws?Exception?{//?獲得請(qǐng)求路徑的uriString?uri?=?request.getRequestURI();//?判斷路徑是登出還是登錄驗(yàn)證,是這兩者之一的話執(zhí)行Controller中定義的方法if(uri.endsWith("/login/auth")?||?uri.endsWith("/login/out"))?{return?true;}//?進(jìn)入登錄頁面,判斷session中是否有key,有的話重定向到首頁,否則進(jìn)入登錄界面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;}//?最后的情況就是進(jìn)入登錄頁面response.sendRedirect(request.getContextPath()?+?"/login");return?false;}}登錄Controller:
@Controller @RequestMapping(value?=?"/login") public?class?LoginController?{@RequestMapping(value?=?{"/",?""})public?String?index()?{return?"login";}@RequestMapping("/auth")public?String?auth(@RequestParam?String?username,?HttpServletRequest?req)?{req.getSession().setAttribute("loginUser",?username);return?"redirect:/index";}@RequestMapping("/out")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方法對(duì)于地址“/login/auth”和"/login/out"不處理。
因此,可以寫點(diǎn)配置,少寫帶java代碼。在攔截器配置中添加2個(gè)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)上很多人會(huì)問為什么攔截器執(zhí)行preHandle方法返回false之后還是會(huì)執(zhí)行afterCompletion方法,其實(shí)我們看下源碼就知道了。
關(guān)于異步請(qǐng)求方面的攔截器以及第二種配置方法(interceptors集合屬性可加入繼承自HandlerInterceptorAdapter抽象類的類以及實(shí)現(xiàn)WebRequestInterceptor接口的類),讀者可自行研究。
總結(jié)
以上是生活随笔為你收集整理的面试官:给我说一下 Spring MVC 拦截器的原理?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个简单案例,带你看懂GC日志!
- 下一篇: SpringBoot 缓存之 @Cach