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處理請求,可以看到,請求方法夾在applyPreHandleapplyPostHandle之間

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM