1.前言
SpringMVC是目前J2EE平台的主流Web框架,不熟悉的園友可以看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧
本文將通過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC攔截器的工作原理並自定義攔截器
2.源碼分析
進入SpringMVC核心類DispatcherServlet的doDispatch方法,在SpringMVC源碼閱讀:核心分發器DispatcherServlet曾經分析過,這里再分析一遍

936行獲得HandlerExecutionChain,含有HandlerMethod和interceptorList
943行根據HandlerExecutionChain獲取RequestMappingHandlerAdapter

958行如果HandlerExecutionChain需要執行下一個攔截器,則返回True
963HandlerAdapter調用Handler處理請求,可以看到,請求方法夾在applyPreHandle和applyPostHandle之間
980行processDispatchResult會調用triggerAfterCompletion,不拋出異常
983和986行調用triggerAfterCompletion會拋出異常
重點看下936行getHandler方法,點進去

1156行HandlerMapping調用getHandler方法獲取HandlerExecutionChain
對着getHandler ctrl+alt+b跳轉到HandlerMapping接口方法實現處,在AbstractHandlerMapping類中

352行獲取HandlerMethod
365行獲取HandlerExecutionChain
366行對跨域請求進行攔截處理
點進去365行的getHandlerExecutionChain方法

417行獲取requestmapping請求路徑
419行判斷HandlerInterceptor是不是MappedInterceptor類型,不是則直接向HandlerExecutionChain加入HandlerInterceptor
HandlerInterceptor是MappedInterceptor類型,需要檢驗是否匹配,最后向HandlerExecutionChain加入HandlerInterceptor
getCorsHandlerExecutionChain方法獲取跨域的HandlerExecutionChain和getHandlerExecutionChain同理,園友可自行分析
現在看看HandlerExecutionChain

主要看applyPostHandle、applyPreHandle和triggerAfterCompletion方法
打開applyPostHandle方法

130行接收HandlerInterceptor數組
134行HandlerInterceptor的preHandle方法執行失敗依然會執行HandlerExecutionChain的triggerAfterCompletion方法 
triggerAfterCompletion方法在所有攔截器preHandle方法成功執行返回True后才會執行(觸發afterCompletion)

3.實例
設置自定義攔截器,繼承HandlerInterceptorAdapter

public class LoginInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲得請求路徑的uri String uri = request.getRequestURI(); // 判斷路徑是登出還是登錄驗證,是這兩者之一的話執行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,有的話繼續用戶的操作 if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) { return true; } // 最后的情況就是進入登錄頁面 response.sendRedirect(request.getContextPath() + "/login/login"); return false; } }
測試Controller
@Controller @RequestMapping(value = "/login") public class LoginController { //@RequestMapping(value = {"/", ""}) @RequestMapping(value = {"login"}) @ResponseBody public String login() { return "login"; } @RequestMapping("/auth") public String auth(@RequestParam String username, HttpServletRequest req) { req.getSession().setAttribute("loginUser", username); return "redirect:/"; } @RequestMapping("/out") public String out(HttpServletRequest req) { req.getSession().removeAttribute("loginUser"); return "redirect:/login/login"; } }
在dispatcher-servlet.xml配置攔截器
因為我們使用了<mvc:annotation-driven/>注解,SpringMVC源碼閱讀:Json,Xml自動轉換提到過
<mvc:annotation-driven/>自動幫我們注冊了
- RequestMappingHandlerMapping
- RequestMappingHandlerAdapter
- ExceptionHandlerExceptionResolver
所以只要在RequestMappingHandlerMapping中配置interceptors屬性

interceptors屬性來自於RequestMappingHandlerMapping的父類AbstractHandlerMapping
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <bean class="org.format.demo.interceptor.LoginInterceptor"/> </property> </bean>
現在瀏覽器輸入任何路徑,都會跳轉到http://localhost:8080/springmvcdemo/login/login,說明攔截器已經生效
瀏覽器輸入http://localhost:8080/springmvcdemo/login/auth?username=ss,給HttpSession設置Attribute,返回主界面
瀏覽器輸入http://localhost:8080/springmvcdemo/login/out,將HttpSession的Attribute移除,重定向到http://host:port/contextPath/login/login
我們還可以通過<mvc:interceptors>標簽來配置攔截器,此時不需要再配置RequestMappingHandlerMapping的interceptors屬性
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login/out"/> <mvc:exclude-mapping path="/login/auth"/> <bean class="org.format.demo.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>
LoginInterceptor這段代碼和mvc:exclude-mapping功能一致,是指不攔截的請求路徑,可以注釋掉

運行效果和剛才一致
講一下為什么可以這么配置攔截器,mvc:interceptors由InterceptorsBeanDefinitionParser解析,該類實現了BeanDefinitionParser,我在SpringMVC源碼閱讀:Json,Xml自動轉換分析過mvc:annotation-driven如何由AnnotationDrivenBeanDefinitionParser解析,道理是類似的,
核心方法是parse,在InterceptorsBeanDefinitionParser的parse方法打斷點驗證一下

和我們用mvc:interceptors標簽配置的內容一致
4.總結
HandlerExecutionChain由Handler對象和Handler攔截器組成,由HandlerMapping的getHandler方法返回,RequestMappingHandlerMapping將adaptedInterceptors傳遞給HandlerExecutionChain的interceptorList
HandlerInterceptor接口允許自定義Handler執行鏈,並為Handler注冊已存在或者自定義的攔截器
AbstractHandlerMapping是HandlerMapping的抽象類,支持優先級排序、默認的Handler和handler攔截器
HandlerMapping根據請求信息調用getHandler方法獲取HandlerExecutionChain
DispatcherServlet的doDispatch方法處理HandlerExecutionChain,該類含有HandlerMethod和interceptorList,在applyPreHandle和applyPostHandle方法之間調用Handler,triggerAfterCompletion最后運行
5.參考
https://docs.spring.io/spring/docs/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中難免有不足,還望指出
