SpringMVC源碼閱讀:攔截器

1.前言

SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html

本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC攔截器的工做原理並自定義攔截器java

2.源碼分析

進入SpringMVC核心類DispatcherServlet的doDispatch方法,在SpringMVC源碼閱讀:核心分發器DispatcherServlet曾經分析過,這裏再分析一遍git

936行得到HandlerExecutionChain,含有HandlerMethod和interceptorListgithub

943行根據HandlerExecutionChain獲取RequestMappingHandlerAdapterweb

958行若是HandlerExecutionChain須要執行下一個攔截器,則返回Truespring

963HandlerAdapter調用Handler處理請求,能夠看到,請求方法夾在applyPreHandleapplyPostHandle之間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)

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/>自動幫咱們註冊了

  1. RequestMappingHandlerMapping
  2. RequestMappingHandlerAdapter
  3. 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/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

https://docs.spring.io/spring/docs/current/javadoc-api/

https://github.com/spring-projects/spring-framework

文中不免有不足,還望指出

相關文章
相關標籤/搜索