SpringMVC系列 教程(六):拦截器详解(附源码分析)

程序员知识码头

共 5265字,需浏览 11分钟

 ·

2019-12-08 23:29

4cd04569e7479d867ace99272486dcc0.webp

7f99125a93988f36d638789f03ec06fa.webp

来源:https://www.cnblogs.com/

fangjian0423/p/springMVC-interceptor.html

一. 前言

SpringMVC是目前主流的Web MVC框架之一。 

拦截器是每个Web框架必备的功能,也是个老生常谈的主题了。

本文将分析SpringMVC的拦截器功能是如何设计的,让读者了解该功能设计的原理。

二. 重要接口及类介绍


1. HandlerExecutionChain类

  由HandlerMethod和Interceptor集合组成的类,会被HandlerMapping接口的getHandler方法获取。

bdb1765daa923446f7799d02bd77bfeb.webp

2. HandlerInterceptor接口

  a453ffd26a08f7f9b489fefeecdefc3f.webp

  SpringMVC拦截器基础接口。 

3. AbstractHandlerMapping

  HandlerMapping的基础抽象类。

fab1159285561de83599e93097ef48bb.webp

4. AsyncHandlerInterceptor

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

5. HandlerInterceptorAdapter

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

6. WebRequestInterceptor

  与HandlerInterceptor接口类似,区别是WebRequestInterceptor的preHandle没有返回值。还有WebRequestInterceptor是针对请求的,接口方法参数中没有response。

  fdf6a0649cb9e1bacad28792f7ec836d.webp

  AbstractHandlerMapping内部的interceptors是个Object类型集合。处理的时候判断为MappedInterceptor[加入到mappedInterceptors集合中];HandlerInterceptor、WebRequestInterceptor(适配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]

7. MappedInterceptor

  一个包括includePatterns和excludePatterns字符串集合并带有HandlerInterceptor的类。很明显,就是对于某些地址做特殊包括和排除的拦截器。

   e9ec1ec5acfa3f2a42096e5b95a137e0.webp

8. ConversionServiceExposingInterceptor

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

三. 源码分析


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

Web请求被DispatcherServlet截获后,会调用DispatcherServlet的doDispatcher方法。

fc0ca00f12c3d43ea8022ad7f5872786.webp

c57147af1909214382ce308453b02cfc.webp

很明显地看到,在HandlerAdapter处理之后,以及处理完成之后会调用HandlerExecutionChain的方法。

HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion方法如下:

edee00ec52d859b86b1402ffac15cec4.webp

24f1399e995b8a88c752164db2922ed6.webp

ee1873198135216ba192a5362c65c991.webp

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

 

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

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

 HandlerMapping的基础抽象类AbstractHandlerMapping中:

1e483b4a98b2ee6b3f09a5d25c977801.webp

7c8e435ef66e7b6c4412ef9ea1600384.webp

我们看到,HandlerExecutionChain的拦截器是从AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors属性中获取的。

四. 拦截器的配置


清楚了HandlerExecutionChain的拦截器属性如何构造之后,下面来看下SpringMVC是如何配置拦截器的。

1. *-dispatcher.xml配置文件中添加 配置

<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>

这里配置的每个都会被解析成MappedInterceptor。

其中子标签会被解析成MappedInterceptor的includePatterns属性;会被解析成MappedInterceptor的excludePatterns属性;会被解析成MappedInterceptor的interceptor属性。

这个标签是被InterceptorsBeanDefinitionParser类解析。

d167d203e346efc4d24638089d269e54.webp

2. 配置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集合中。

  1cab3da2c248aab1ac856938d375defb.webp

  0c52e098edb71db36084dabc8b281a59.webp

  如果读者配置了:

  那么配置如下:

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接口的类),读者可自行研究。

文中难免有错误,希望读者能够指出来。


- End -

ed0b022a3d073ff6f0979d0734708fad.webp


面试题系列教程  点击--> 面试题技术干货连载目录 跳转


Maven系列教程  点击--> Maven技术干货连载目录 跳转


MyBatis系列教程  点击--> MyBatis技术干货连载目录 跳转


JVM调优总结系列教程  点击--> JVM调优技术干货连载目录 跳转





, a514e826b619d8e428016d74ac0ea58b.webp

浏览 66
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报