Spring MVC拦截器的原理

📖摘要


今天分享下 —— Spring MVC 拦截器的原理 的一些基本知识,欢迎关注!

拦截器是每个 Web 框架必备的功能,也是个老生常谈的主题了,这里来分析 SpringMVC 的拦截器功能是如何设计的,让你了解该功能设计的原理


🌂分享


💕重要接口及类介绍
  1. HandlerExecutionChain
    HandlerMethodInterceptor 集合组成的类,会被 HandlerMapping 接口的 getHandler 方法获取。
    在这里插入图片描述

  2. HandlerInterceptor 接口 在这里插入图片描述
    SpringMVC 拦截器基础接口。

  3. AbstractHandlerMapping
    HandlerMapping 的基础抽象类。在这里插入图片描述

  4. AsyncHandlerInterceptor
    继承 HandlerInterceptor 的接口,额外提供了 afterConcurrentHandlingStarted 方法,该方法是用来处理异步请求。当 Controller 中有异步请求方法的时候会触发该方法。楼主做过测试,异步请求先支持 preHandle 、然后执行 afterConcurrentHandlingStarted。异步线程完成之后执行 preHandlepostHandle、afterCompletion 。有兴趣的读者可自行研究。

  5. HandlerInterceptorAdapter
    实现 AsyncHandlerInterceptor 接口的抽象类,一般我们使用拦截器的话都会继承这个类。然后复写相应的方法。

  6. WebRequestInterceptor
    HandlerInterceptor 接口类似,区别是 WebRequestInterceptorpreHandle 没有返回值。还有 WebRequestInterceptor 是针对请求的,接口方法参数中没有 response在这里插入图片描述
      
    AbstractHandlerMapping 内部的 interceptors 是个 Object 类型集合。处理的时候判断为 MappedInterceptor [加入到 mappedInterceptors 集合中]; HandlerInterceptor、WebRequestInterceptor (适配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]

  7. MappedInterceptor
    一个包括 includePatternsexcludePatterns 字符串集合并带有 HandlerInterceptor 的类。很明显,就是对于某些地址做特殊包括和排除的拦截器。在这里插入图片描述

  8. ConversionServiceExposingInterceptor
    默认的 <annotation-driven/> 标签初始化的时候会初始化 ConversionServiceExposingInterceptor 这个拦截器,并被当做构造方法的参数来构造 MappedInterceptor 。之后会被加入到 AbstractHandlerMappingmappedInterceptors 集合中。该拦截器会在每个请求之前往 request 中丢入 ConversionService 。主要用于 spring:eval 标签的使用。


🤞源码分析

首先我们看下拦截器的如何被调用的。

Web 请求被 DispatcherServlet 截获后,会调用 DispatcherServletdoDispatcher 方法。
在这里插入图片描述
在这里插入图片描述
很明显地看到,在 HandlerAdapter 处理之后,以及处理完成之后会调用 HandlerExecutionChain 的方法。

HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion

方法如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

很明显,就是调用内部实现 HandlerInterceptor 该接口集合的各个对应方法。

下面我们看下 HandlerExecutionChain 的构造过程。

HandlerExecutionChain 是从 HandlerMapping 接口的 getHandler 方法获取的。

HandlerMapping 的基础抽象类 AbstractHandlerMapping 中:
在这里插入图片描述
在这里插入图片描述
我们看到,HandlerExecutionChain 的拦截器是从 AbstractHandlerMapping 中的 adaptedInterceptorsmappedInterceptors 属性中获取的。


✌清楚了HandlerExecutionChain的拦截器属性如何构造之后,下面来看下SpringMVC是如何配置拦截器的。
  1. *-dispatcher.xml配置文件中添加 mvc:interceptors配置
<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/login"/>   
    <mvc:exclude-mapping path="/index"/>
    <bean class="package.interceptor.XXInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

这里配置的每个 <mvc:interceptor> 都会被解析成 MappedInterceptor

其中子标签 <mvc:mapping path="/**"/> 会被解析成 MappedInterceptorincludePatterns 属性;<mvc:exclude-mapping path="/**"/> 会被解析成 MappedInterceptorexcludePatterns 属性;<bean/> 会被解析成 MappedInterceptorinterceptor 属性。

<mvc:interceptors> 这个标签是被 InterceptorsBeanDefinitionParser 类解析。
在这里插入图片描述

  1. 配置 RequestMappingHandlerMapping,并配置该 bean 对应的 interceptors集合属性。这里的 interceptors 集合是个 Object 类型的泛型集合。

AbstractHandlerMapping 抽象类只暴露了1个拦截器的set方法 -> interceptors

adaptedInterceptors和mappedInterceptors 均没有暴露 set 方法,因此我们只能为 RequestMappingHandlerMapping 配置 interceptors 属性。

其实 AbstractHandlerMapping 内部的 initInterceptors 方法中,会遍历 interceptors 集合,然后判断各个项是否是 MappedInterceptor、HandlerInterceptor、WebRequestInterceptor

其中 MappedInterceptor 类型的拦截器会被加到 mappedInterceptors 集合中, HandlerInterceptor 类型的会被加到 adaptedInterceptors 集合中, WebRequestInterceptor 类型的会被适配成 WebRequestHandlerInterceptorAdapter 加到 adaptedInterceptors 集合中。

在这里插入图片描述
在这里插入图片描述

如果配置了:

<mvc:annotation-driven/>

那么配置如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
  <property name="interceptors">
    <bean class="package.interceptor.XXInterceptor"/>
  </property>  
  <property name="order" value="-1"/>
</bean>

否则,可以去掉order这个属性的设置。

一般建议使用第一种方法。

编写自定义的拦截器

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");
        return false;
  }
  
}

登录Controller:

@Controller
@RequestMapping(value = "/login")
public class LoginController {
  
    @RequestMapping(value = {"/", ""})
    public String index() {
        return "login";
    }

    @RequestMapping("/auth")
    public String auth(@RequestParam String username, HttpServletRequest req) {
        req.getSession().setAttribute("loginUser", username);
        return "redirect:/index";
    }

    @RequestMapping("/out")
    public String out(HttpServletRequest req) {
        req.getSession().removeAttribute("loginUser");
        return "redirect:/login";
    }
  
}

*-diapatcher.xml配置:

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="org.format.demo.interceptor.LoginInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

**PS:**我们看到 LoginInterceptor 里的 preHandle 方法对于地址 “/login/auth”"/login/out" 不处理。

因此,可以写点配置,少写带 java 代码。在拦截器配置中添加2个 exclude-mapping ,并且去掉 LoginInterceptor 里的

if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
  return true;
}

配置新增:

<mvc:exclude-mapping path="/login/out"/>
<mvc:exclude-mapping path="/login/auth"/>

SpringMVC 拦截器的原理以及各种配置,像网上很多人会问为什么拦截器执行 preHandle 方法返回 false 之后还是会执行 afterCompletion 方法,其实我们看下源码就知道了。

关于异步请求方面的拦截器以及第二种配置方法( interceptors 集合属性可加入继承自 HandlerInterceptorAdapter 抽象类的类以及实现 WebRequestInterceptor 接口的类),读者可自行研究。


🎉最后

  • 更多参考精彩博文请看这里:《陈永佳的博客》

  • 喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈永佳

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值