zoukankan      html  css  js  c++  java
  • 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

    文中难免有不足,还望指出

  • 相关阅读:
    jsonp原理
    Mysql FUNCTION 示例
    数据库设计心得
    《杀死一只知更鸟》读后感
    《必然》读后感
    承接各种字牌算法
    关于麻将的算法构想
    十三水最优组合问题快速运算求解方案
    如何表示扑克牌?
    如何比较牌大小?
  • 原文地址:https://www.cnblogs.com/Java-Starter/p/10352802.html
Copyright © 2011-2022 走看看