SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html
本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC攔截器的工做原理並自定義攔截器java
進入SpringMVC核心類DispatcherServlet的doDispatch方法,在SpringMVC源碼閱讀:核心分發器DispatcherServlet曾經分析過,這裏再分析一遍git
936行得到HandlerExecutionChain,含有HandlerMethod和interceptorListgithub
943行根據HandlerExecutionChain獲取RequestMappingHandlerAdapterweb
958行若是HandlerExecutionChain須要執行下一個攔截器,則返回Truespring
963HandlerAdapter調用Handler處理請求,能夠看到,請求方法夾在applyPreHandle和applyPostHandle之間api
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)
設置自定義攔截器,繼承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中配置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標籤配置的內容一致
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最後運行
https://docs.spring.io/spring/docs/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中不免有不足,還望指出