拦截器是所有Web框架必备的功能,也是一个老生常谈的主题。
本文分析了SpringMVC的拦截器功能是如何设计的,使读者了解其功能设计的原理。
重要接口和类介绍
1.HandlerExecutionChain类
由HandlerMethod和Interceptor集合组成的类是通过HandlerMapping接口的getHandler方法获取的。
2.handler解释器界面
SpringMVC拦截器基础界面。
3. AbstractHandlerMapping
HandlerMapping的基础抽象类。
4.AsyncHandlerInterceptor
继承了HandlerInterceptor的接口,并添加了用于处理异步请求的afterConcurrentHandlingStarted方法。 如果Controller中存在异步请求方法,将触发此方法。 虽然楼主已经过测试,但是异步请求首先支持预句柄,然后运行异步命令开始。 异步线程完成后,执行preHandle、postHandle和afterCompletion。 感兴趣的读者可以自己研究。
5.HandlerInterceptorAdapter
实现AsyncHandlerInterceptor接口的抽象类一般在我们使用拦截器时继承该类。 然后重写适当的方法。
6.webrequest解释器
它与HandlerInterceptor界面类似,不同之处在于WebRequestInterceptor的预句柄没有返回值。 此外,WebRequestInterceptor用于请求,接口方法参数没有响应。
AbstractHandlerMapping内部的interceptors是对象类型的集合。 处理时映射解释器[添加到映射解释器集合]; HandlerInterceptor,webrequest interceptor (符合webrequesthandlerinterceptoradapter )添加到adaptedInterceptors
7 .映射解释器
包含includePatterns和excludePatterns字符串集合且具有HandlerInterceptor的类。 很明显,这是一个对特定地址进行特殊添加和排除的拦截程序。
8 .转换器服务处理器
初始化默认标签后,将初始化名为conversionserviceexposinginterceptor的拦截器,并构建映射解释器作为生成方法的参数。 然后将其添加到AbstractHandlerMapping的映射解释集合中。 拦截器针对每个请求在request中投入转换服务。 主要用于spring:eval标签的使用。
源代码分析
我们先来看看拦截器的调用方法。
当Web请求被doDispatcher servlet截获时,将调用do dispatcher servlet的do dispatcher方法。
很明显,在HandlerAdapter处理之后和处理完成之后,将调用HandlerExecutionChain的方法。
HandlerExecutionChain的applyPreHandle、applyPostHandle和triggerAfterCompletion方法如下。
很明显,这是调用在内部实现HandlerInterceptor的接口集合的各对应方法。
让我们来看看HandlerExecutionChain的结构过程。
HandlerExecutionChain是从HandlerMapping接口的getHandler方法获取的。
在HandlerMapping的基础抽象类AbstractHandlerMapping中:
HandlerExecutionChain中的拦截器来自AbstractHandlerMapping的自适应解释器和映射解释器属性
中获取的。拦截器的配置
清楚了HandlerExecutionChain的拦截器属性如何构造之后,下面来看下SpringMVC是如何配置拦截器的。
1. *-dispatcher.xml配置文件中添加 配置
这里配置的每个都会被解析成MappedInterceptor。
其中子标签会被解析成MappedInterceptor的includePatterns属性;会被解析成MappedInterceptor的excludePatterns属性;会被解析成MappedInterceptor的interceptor属性。
这个标签是被InterceptorsBeanDefinitionParser类解析。
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集合中。
如果读者配置了:
那么配置如下:
否则,可以去掉order这个属性的设置。
一般建议使用第一种方法。
编写自定义的拦截器
public class LoginInterceptor extendsHandlerInterceptorAdapter {
@Overridepublic booleanpreHandle(HttpServletRequest request, HttpServletResponse response,
Object handler)throwsException {//获得请求路径的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 classLoginController {
@RequestMapping(value= {"/", ""})publicString index() {return "login";
}
@RequestMapping("/auth")publicString auth(@RequestParam String username, HttpServletRequest req) {
req.getSession().setAttribute("loginUser", username);return "redirect:/index";
}
@RequestMapping("/out")publicString out(HttpServletRequest req) {
req.getSession().removeAttribute("loginUser");return "redirect:/login";
}
}
*-diapatcher.xml配置:
PS:我们看到LoginInterceptor里的preHandle方法对于地址“/login/auth”和"/login/out"不处理。
因此,可以写点配置,少写带java代码。在拦截器配置中添加2个exclude-mapping,并且去掉LoginInterceptor里的
if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {return true;
}
配置新增:
总结
总结了SpringMVC拦截器的原理以及各种配置,像网上很多人会问为什么拦截器执行preHandle方法返回false之后还是会执行afterCompletion方法,其实我们看下源码就知道了。
关于异步请求方面的拦截器以及第二种配置方法(interceptors集合属性可加入继承自HandlerInterceptorAdapter抽象类的类以及实现WebRequestInterceptor接口的类),读者可自行研究。
原文:
https://www.cnblogs.com/fangjian0423/p/springMVC-interceptor.html