本文主要是作者学习spring中的拦截器的一些简要记录。
拦截器是spring中的一个重要概念。他被注册到spring,拦截指定规则的请求,基于回调机制执行。一般来说,拦截器只会拦截action请求,这一点与过滤器不同。
下面贴一张spring web请求的执行流程图:
因为拦截器主要是对controller起作用,所以一般在webapp/WEB-INF
下的类似spring-web-servlet.xml
这样的配置文件中定义拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/main/*"/>
<bean class="com.chengc.demos.web.demo1.interceptor.FirstInterceptor"/>
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/main/*"/>
<bean class="com.chengc.demos.web.demo1.interceptor.SecondInterceptor">bean>
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/main/*"/>
<bean class="com.chengc.demos.web.demo1.interceptor.ThirdInterceptor">bean>
mvc:interceptor>
mvc:interceptors>
Web容器在初始化DispatcherServlet(父类为FrameworkServlet)时,会创建其独有的XmlWebApplicationContext
。在初始化该context,FrameworkServlet.configureAndRefreshWebApplicationContext
中有一句wac.refresh()
时,会在finishBeanFactoryInitialization(beanFactory)
方法中调用AbstractHandlerMapping.initApplicationContext
方法:
private final List<Object> interceptors = new ArrayList<Object>();
private final List<MappedInterceptor> mappedInterceptors = new ArrayList<MappedInterceptor>();
protected void initApplicationContext() throws BeansException {
// 默认空实现
extendInterceptors(this.interceptors);
// 找到定义的interceptor
detectMappedInterceptors(this.mappedInterceptors);
// 对Interceptors进行初始化
initInterceptors();
}
protected void detectMappedInterceptors(List<MappedInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
getApplicationContext(), MappedInterceptor.class, true, false).values());
}
该方法就会去初始化所有interceptor,调用他们的
和
。
先经过过滤器(机会多所有请求进行过滤),然后才会到拦截器。
doDispatch
分发:protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// 传给ViewResolver进行视图解析的对象
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 为当前请求准备好HandlerExecutionChain
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 为当前请求准备好HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用handler执行链上的interceptor的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// 真正调用handler(Controller)处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// 逆序调用handler执行链上的interceptor的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
getHandler
主要是为当前request创建一个包含了若干路径匹配的interceptor和一个handler(即Controller)的HandlerExecutionChain(执行链)。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这里的handlerMappings就包含了最常用的RequestMappingHandlerMapping
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
这里看看hm.getHandler
,在AbstractHandlerMapping.getHandler
方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这一步就是根据request的路径找到Controller
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
// 正常的Controller应该是HandlerMethod类
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
// 获取该Controller的执行链
return getHandlerExecutionChain(handler, request);
}
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 请求的相对路径,比如/main/index
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
// 这一步会真正创建含有目标handler和Method的HandlerMethod对象
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// 将我们前面提到的注册的拦截器读取遍历
for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
// 如果拦截器的mapping和请求的路径匹配上了,就添加到该Handler的执行链中
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
// 最后得到加上了拦截器的HandlerExecutionChain
return chain;
}
这里,HandlerChain已经加入了interceptor:
为当前请求创建合适的HandlerAdapter,准备开始执行handler:
一般Controller返回的就是RequestMappingHandlerAdapter
。
这个阶段会调用handler执行链上的interceptor的preHandle方法。
到HandlerExecutionChain.applyPreHandle
方法,这里就会调用所有符合该请求的路径映射的interceptor的preHandle
方法:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (getInterceptors() != null) {
// 遍历执行链上的Interceptor
for (int i = 0; i < getInterceptors().length; i++) {
HandlerInterceptor interceptor = getInterceptors()[i];
// 这里就在调用每个interceptor的preHandle方法了
if (!interceptor.preHandle(request, response, this.handler)) {
// interceptor返回true代表给下一个handler,
// 返回false代表调用链结束,直接调用handlers.afterCompletion方法.
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
这个阶段利用handler适配器真正调用handler执行代码逻辑。
我们直接看最关键的类InvocableHandlerMethod
:
private Object invoke(Object... args) throws Exception {
// 将handler的目标方法设为可访问
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
// 对目标handler实例调用目标方法
return getBridgedMethod().invoke(getBean(), args);
}
}
可以看到,SpringMVC内是通过反射的方式访问目标handler的方法。
以上的getBridgedMethod
和getBean
两个方法都来自于该类的父类HandlerMethod
,存储了目标handler的Bean和Method。
这里返回的信息可以是ModelAndView
, 代表视图路径的字符串,纯信息字符串等。还需要进一步加工处理。经过加工后,都转为ModelAndView
。
逆序调用handler执行链上的interceptor的postHandle方法:HandlerExecutionChain.applyPostHandle
。
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
if (getInterceptors() == null) {
return;
}
// 这里就能看到,为什么说是逆序调用postHandle了
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
该步骤主要是处理ModelAndView,ViewResolver解析后得到View
。然后将该View处理后,放入response,最终返回给客户端。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 请求来源的区域
Locale locale = this.localeResolver.resolveLocale(request);
// 给response也用相同区域
response.setLocale(locale);
View view;
if (mv.isReference()) {
// mv指向视图的索引,需要ViewSolver解析
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("...")
}
}
else {
// ModelAndView本身就包含真实的视图对象
view = mv.getView();
if (view == null) {
throw new ServletException("...");
}
}
// Delegate to the View object for rendering.
// 解析视图,放入
try {
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
该步骤作为收尾步骤,主要是逆序执行interceptors的afterCompletion
方法:HandlerExecutionChain.triggerAfterCompletion
。
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
if (getInterceptors() == null) {
return;
}
// 逆序遍历执行interceptor的afterCompletion方法
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
在Spring里,配置一个拦截器的常用方式是实现HandlerInterceptor
接口。那我们可以通过看看该接口的代码以及注释对spring拦截器有更深了解。
下面先贴一份出去了注释部分的纯源码:
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
可以看到这个接口相当简洁,就定以了3个方法。import
的前两个类
HttpServletRequest
和HttpServletResponse
我们也很熟悉,属于javax.servlet
包。下面开始细讲。
接口注释很长,我这里把doc文档翻译整理下:
这是一个工作流接口,它允许自定义的handler
执行链。应用可以为指定的handler组注册任意数量的自带的或是自定义的拦截器,这样能不修改handler实现代码就可以为之添加通用的提前处理功能。
适合的HandlerAdapter
在触发handler
(即SpringMVC中的Controller
)执行前会先调用HandlerInterceptor
。这个机制可以被用于大范围的预处理前,例如认证授权检查或是常见的handler行为如区域设置和主题更改。总的来说,拦截器的主要作用就是提出共用的处理代码,减少代码冗余。
在异步处理的场景中,handler可在一个单独线程中执行,与此同时主线程在没有调用postHandler
和afterCompletion
回调的情况下就退出了。当并发的handler执行完成后,此次请求会被回派,这样做的目的是呈现model
而且该合同的所有方法又会被调用一次。关于异步场景的更多信息请查看org.springframework.web.servlet.AsyncHandlerInterceptor
。
一般来说,每个HandlerMapping bean
定义一个拦截器链。为了将某个拦截器链应用于一组handlers,需要通过一个HandlerMapping bean来映射到期望的handlers。拦截器本身是在application context
内被定义的bean,mapping bean
通过 interceptors
属性引用。
拦截handler执行。拦截器利用preHandle方法对handler进行预处理。
在preHandle方法中,每个拦截器都能决定是(return false)否(return true)要终止执行链,在决定要终止时通常是发送HTTP错误或是返回一个自定义响应。
以下是该方法定义:
/**
* @param request 当前 HTTP 请求
* @param response 当前 HTTP 响应
* @param handler 被选择执行的handler,用于类型 和/或 实例评估
* @return true 时代表执行链应该继续调用下一个拦截器或是handler本身;
* 如果 return false,DispatcherServlet会假定此拦截器自己已经处理了响应。
* @throws Exception 当发生错误时
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
preHandle
方法是进行处理器拦截用的,顾名思义,该方法将在HandlerMapping
选定一个适合的handler(Controller)对象之后,HandlerAdapter
调用handler(Controller)之前调用。
Spring中DispatcherServlet链式处理多个拦截器,一个调用链中可以同时存在多个拦截器,且将该handler本身作为执行链的末尾元素。Spring会根据过滤器定义的前后顺序正序地执行拦截器的preHandle
方法。
Spring的这种Interceptor链式结构也是可以进行中断的,这种中断方式是令preHandle的返回值为false,也就是说当preHandle方法的返回值为false的时候整个请求就结束了。
拦截handler执行。拦截器利用postHandle方法对handler进行"后处理"。
以下是该方法定义:
/**
* @param request 当前 HTTP 请求
* @param response 当前 HTTP 响应
* @param handler handler 或 HandlerMethod 开启异步执行,用于类型 和/或 实例检查
* @param modelAndView 由handler返回的ModelAndView,可能为空
* @throws Exception in case of errors
*/
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
注意:postHandle方法只会在当前这个拦截器的preHandle
方法成功完成且返回值为true的时候才会执行。
在HandlerAdapter实际调用handler(controller)之后,DispatcherServlet
渲染视图之前调用postHandle
方法。可以通过给定的ModelAndView暴露额外的model
对象给视图。
注意,与preHandle
方法不同,DispatcherServlet以执行链的相反顺序调用postHandle
方法,也就是说先声明的拦截器拥有的postHandle方法反而会被后调用。
在handler任意输出的情况下都会调用afterCompletion
方法,因此可被用来做适当的资源清理。
以下是该方法定义:
/**
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler (or {@link HandlerMethod}) that started async
* execution, for type and/or instance examination
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
注意:afterCompletion方法只会在当前这个拦截器的preHandle
方法成功完成且返回值为true的时候才会执行。
在对请求的处理完成后进行回调,也就是在渲染视图之后。
与postHandle
方法相同,DispatcherServlet以执行链的相反顺序调用afterCompletion
方法,也就是说先声明的拦截器拥有的afterCompletion方法反而会被后调用。
public class MyCharsetFilter implements Filter
所有servlet过滤器都必须实现javax.servlet.Filter
接口。也就是说,他跟servlet相关,而跟Spring无直接关联。
根据JDK Doc定义,Filter过滤器被定义为:对到资源的请求或来自资源的响应进行过滤任务的对象。
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
init
方法可以读取web.xml中的Servlet过滤器的配置初始化参数,所以一般是在init 方法内执行一些初始化内容。public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain )
throws IOException, ServletException;
doFilter
方法。chain.doFilter
交给执行链中下一个对象继续处理,或是不这么干以阻塞请求处理。doFilter的最终目的只有一个,调用internalDoFilter,中间可能会增加一些安全策略,估计Globals.IS_SECURITY_ENABLE与是否开启https服务有关,具体没仔细研究过。
internalDoFilter的最终目的也只有一个,就是调当前pos指向的过滤器链中的某一个filter的doFilter(request, response, this)方法,中间可能会增加一些安全策略,以及当所有过滤器调用完了,进行的一些收尾清理工作,包括调用servlet.service(request, response)方法,来处理真正的请求,以及清除threadLocal中保存的当前的request和response,为下一次请求做准备。
public void destroy();
doFilter
方法内的所有线程已经退出或是时间阈值已经超过)调用destroy方法(仅一次)最后给一个简单的过滤器流程图:
<filter>
<filter-name>filterfilter-name>
<filter-class>com.chengc.demos.web.demo1.filters.MyCharsetFilterfilter-class>
<init-param>
<param-name>charsetparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>contentTypeparam-name>
<param-value>text/html;charset=UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>filterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
public class MyCharsetFilter implements Filter {
private static final Logger logger = LoggerFactory
.getLogger(MyCharsetFilter.class);
static {
System.out.println("MyCharsetFilter static");
}
public MyCharsetFilter(){
System.out.println("MyCharsetFilter initiated");
}
private FilterConfig config = null;
/**
* Servlet过滤器的初始化方法,Servlet容器创建Servlet过滤器实例后,将调用这个方法。
* 这个方法可以读取web.xml中的Servlet过滤器的配置初始化参数
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
logger.info("MyCharsetFilter初始化...");
String filterName = filterConfig.getFilterName();
// 得到在web.xml文件中配置的初始化参数
String initParam1 = filterConfig.getInitParameter("charset");
String initParam2 = filterConfig.getInitParameter("contentType");
// 返回过滤器的所有初始化参数的名字的枚举集合。
Enumeration<String> initParameterNames = filterConfig
.getInitParameterNames();
logger.info("filterName=" + filterName + ", initParam1=" + initParam1 + ", initParam2=" + initParam2);
while (initParameterNames.hasMoreElements()) {
String paramName = (String) initParameterNames.nextElement();
logger.info(paramName);
}
}
/**
* 这个方法完成实际的过滤操作,当客户请求访问于过滤器关联的URL时,Servlet容器将先调用过滤器的doFilter方法。
* FilterChain参数用于访问后续过滤器
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("before doFilter...");
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
// 强制类型转换
// HttpServletRequest request = (HttpServletRequest)servletRequest;
// HttpServletResponse response = (HttpServletResponse)servletResponse;
// 获取web.xm设置的编码集,设置到Request、Response中
// response.setContentType(config.getInitParameter("contentType"));
// response.setCharacterEncoding(config.getInitParameter("charset"));
// 将请求转发到目的地
filterChain.doFilter(servletRequest, servletResponse);
ApplicationContext ctx2 = ContextLoader.getCurrentWebApplicationContext();
TestService ts = (TestService)ctx2.getBean("testServiceImpl");
ts.sayHello();
logger.info("after doFilter...");
}
/**
* Web容器在销毁过滤器实例前调用该方法,这个方法中可以释放Servlet过滤器占用的资源
*/
@Override
public void destroy() {
logger.info("MyCharsetFilter准备销毁...");
}
}
拦截器Interceptor | 过滤器Filter | |
---|---|---|
原理 | 属于Spring范畴。基于java的反射机制调用handler方法,在其前后调用拦截器的方法 | 属于Servlet规范定义。基于函数回调 |
创建 | 在context.xml中配置。由Spring容器初始化。 | 在web.xml中配置filter基本属性。由web容器创建 |
servlet容器 | 拦截器不直接依赖于servlet容器 | 过滤器依赖于servlet容器 |
作用对象 | 拦截器只能对action请求起作用 | 过滤器则可以对几乎所有的请求起作用 |
访问范围 | 拦截器可以访问action上下文、值栈里的对象,可以获取IOC容器中的各个bean,这点很重要,在拦截器里注入一个service,可以调用业务逻辑; | 过滤器也可以使用ContextLoader.getCurrentWebApplicationContext() 获取到根Context即XmlWebApplicationContext ,注意该context只能访问除了Controller 以外的bean,因Controller在另一个xml中配置 |
使用场景 | 即可用于Web,也可以用于其他Application | 基于Servlet规范,只能用于Web |
使用选择 | 可以深入到方法执行前后,使用场景更广 | 只能在Servlet前后起作用 |
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
logger.info("FirstInterceptor preHandle 你妹");
ServletContext servletContext = request.getSession().getServletContext();
// 这个方法是通过servletContext的
// getAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)拿到context
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
TestService ts0 = (TestService)ctx.getBean("testServiceImpl");
ts0.sayHello();
// 也可以通过这种方式获取ApplicationContext
ApplicationContext ctx2 = ContextLoader.getCurrentWebApplicationContext();
TestService ts = (TestService)ctx2.getBean("testServiceImpl");
ts.sayHello();
// TODO Auto-generated method stub
return true;
}
handler
自身执行选项)以及自定义后处理。web.xml
中配置,web.xml是应用程序上下文中的HandlerInterceptor。