SpringMVC原始碼閱讀:攔截器
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
文中難免有不足,還望指出