Springmvc流程中的扩展点有很多,可以在很多地方插入自己的代码逻辑达到控制流程的目的.

如果要对Controller的handler方法做统一的处理.我想应该会有很多选择,比如:@ModelAttribute @InitBinder @ExceptionHandler @ControllerAdvice等注解,自己写AOP包裹Controller,Interceptor等等..

我从来没有用过Interceptor,所以最近稍微花了点时间研究了下.

流程

先看看在Springmvc中Interceptor拦截器是在什么时候被调用的.

 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
ModelAndView mv = null;
Exception dispatchException = null; try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
} // Determine handler adapter for the current request.
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 (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
} if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} // Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(request, mv);
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
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

从dispatcherServlet中可以看出大致的工作流程:

第44行是我们比较熟悉的,让HandlerAdapter来调用我们自己写的Controller的handler来处理request.

在此之前的39行是调用包装了handler的HandlerExecutionChain的前置处理方法,第51行是调用了HandlerExecutionChain的后置处理方法.

而HandlerExecutionChain的applyPreHandle与applyPostHandle里面会调用拦截器的相关方法.

HandlerExecutionChain

handler方法会和各种Interceptor包装成HandlerExecutionChain给dispatcherServlet调用,所以有必要看看HandlerExecutionChain里的方法.

 /**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
} /**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
} /**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception { HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}

这3个方法刚好对应了HandlerInterceptor里的3个方法:

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;

从Interceptor方法名字中我们就可以看出来这3个方法的作用:handler方法之前调用,handler方法之后调用,dispatch之后调用(视图渲染完之后).

如果配置了多个拦截器,从HandlerExecutionChain中的3个方法里也可以看出:

1.如果前置处理的顺序是拦截器1,拦截器2,拦截器3.那后置处理的顺序是拦截器3,拦截器2,拦截器1

2.如果一个拦截器前置处理的时候返回了false.那么后面的拦截器就不需要执行了.比如拦截2前置方法返回false.那拦截器3就不会执行.

3.在2前置处理失败的情况下,..调用过前置处理方法的拦截器的afterCompletion方法会被调用,用来清理资源...从代码中可以看出afterCompletion和后置处理方法是一样,也是拦截器顺序的倒序执行的,不过只有前置方法返回true的拦截器才会触发(因为拦截器3都没触发,所以不需要触发afterCompletion来清理资源).

多个拦截器的顺序

从HandlerExecutionChain中已经可以看出配置多个拦截器时候的调用顺序了...那怎么确定配置的多个拦截器在HandlerExecutionChain中的加载顺序呢?

实践结果

我实践的结果是在XML里配置的顺序就是加载顺序..

     <mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.labofjet.controller.TestInteceptor1"></bean>
</mvc:interceptor> <mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.labofjet.controller.TestInteceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>

比如这样就是先进入拦截1,再进入拦截2.

分析思考过程

记录一下我的思考过程,便于以后复习:

从DispatcherServlet的doDispatcher放中会调用这个方法:

 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

说明HandlerExecutionChain 来自HandlerMapping,在一般的情况下应该是RequestMappingHandlerMapping.因为RequestMappingHandlerMapping的order是最高的.所以它返回了handler以后就直接作为方法的结果返回了.

     @Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}

L15说明HandlerExecutionChain来自getHandlerExecutionChain方法.

     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)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} return chain;
}

interceptor来么来自adaptedInterceptors要么来自mappedInterceptors.

打断点发现是来自mappedInterceptors,而adaptedInterceptors是给我们自己扩展去加载自定义拦截器用的.

再思考下:

RequestMappingHandlerMapping是初始化Springmvc的时候加载的bean,Controller的handler方法是Springmvc初始化的时候就找出来并包装好的.所以Interceptor没有理由不在Springmvc初始化的时候找出来并包装好.毕竟这handler和interceptor功能都差不多....

我粗略看了下RequestMappingHandlerMapping在初始化的时候似乎只找到了2个地方可以参与Spring声明周期:

第一个是:

 /**
* Detects handler methods at initialization.
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
} /**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
} String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}

afterPropertiesSet这里是为了找到所有handler方法.

第二个是:

     /**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}

这里initApplicationContext就是初始化interceptor用的.

前面实践打断点发现是mappedInterceptors,所以是调用detectMappedInterceptors来加载我定义的拦截器的.

    /**
* Detect beans of type {@link MappedInterceptor} and add them to the list of mapped interceptors.
* <p>This is called in addition to any {@link MappedInterceptor}s that may have been provided
* via {@link #setInterceptors}, by default adding all beans of type {@link MappedInterceptor}
* from the current context and its ancestors. Subclasses can override and refine this policy.
* @param mappedInterceptors an empty list to add {@link MappedInterceptor} instances to
*/
protected void detectMappedInterceptors(List<MappedInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
getApplicationContext(), MappedInterceptor.class, true, false).values());
}

这个方法其实就是把所有MappedInterceptor类型的类找出来而已...我们定义的类型不是MappedInterceptor的子类..想必有什么地方把我们定义的Interceptor放到了MappedInterceptor类中.那是哪里呢? 我没有找到.......可能需要找到XML中字符串<mvc:interceptors>是怎么解析的...

不过这里已经得到了足够的信息,至少可以知道Interceptor读出来以后并没有进行什么排序,所以读出来的顺序应该就是List<MappedInterceptor> mappedInterceptors的顺序.

总结

简单总结一下

拦截器的流程:

请求 -> DispatcherServlet -> 各个拦截器定义顺序来执行前置拦截方法 -> 各个拦截器定义顺序倒序来执行后置拦截方法 -> 处理视图 -> 拦截器定义倒序顺序回收资源

RequestMappingHandlerMapping:

找到所有MappedInterceptor拦截器并设置到属性里.

找到handler方法并设置到属性里.

当有请求到DispatcherServlet的时候把2者结合成HandlerExecutionChain丢给DispatcherServlet去调用.

SpringMVC学习记录5的更多相关文章

  1. springMVC学习记录1-使用XML进行配置

    SpringMVC是整个spring中的一个很小的组成,准确的说他是spring WEB这个模块的下一个子模块,Spring WEB中除了有springMVC还有struts2,webWork等MVC ...

  2. SpringMVC学习记录4

    主题 SpringMVC有很多很多的注解.其中有2个注解@SessionAttributes @ModelAttribute我平时一般不用,因为实在是太灵活了.但是又有一定限制,用不好容易错.. 最近 ...

  3. SpringMVC学习记录3

    这次的主题 最近一直在学习SpringMVC..(这句话我已经至少写了3,4遍了....).这次的研究主要是RequestMappingHandlerAdapter中的各种ArgumentsResol ...

  4. SpringMVC学习记录2

    废话 最近在看SpringMVC...里面东西好多...反正东看一点西看一点吧... 分享一下最近的一些心得..是关于DispatcherServlet的 DispatcherServlet与Cont ...

  5. SpringMVC学习记录

    1E)Spring MVC框架 ①Jar包结构: docs+libs+schema. 版本区别:核心包,源码包. SpringMVC文档学习: 学习三步骤: 1)是什么? 开源框架 2)做什么? IO ...

  6. springMVC学习记录2-使用注解配置

    前面说了一下使用xml配置springmvc,下面再说说注解配置.项目如下: 业务很简单,主页和输入用户名和密码进行登陆的页面. 看一下springmvc的配置文件: <?xml version ...

  7. SpringMVC学习记录1

    起因 以前大三暑假实习的时候看到公司用SpringMVC而不是Struts2,老司机告诉我SpringMVC各种方便,各种解耦. 然后我自己试了试..好像是蛮方便的.... 基本上在Spring的基础 ...

  8. springMVC学习记录3-拦截器和文件上传

    拦截器和文件上传算是springmvc中比较高级一点的内容了吧,让我们一起看一下. 下面先说说拦截器.拦截器和过滤器有点像,都可以在请求被处理之前和请求被处理之到做一些额外的操作. 1. 实现Hand ...

  9. SpringMVC学习记录七——sjon数据交互和拦截器

    21       json数据交互 21.1      为什么要进行json数据交互 json数据格式在接口调用中.html页面中较常用,json格式比较简单,解析还比较方便. 比如:webservi ...

随机推荐

  1. Squirrel: 通用SQL、NoSQL客户端

    安装 配置数据库 配置驱动 配置连接 如果你的工作中,需要使用到多个数据库,又不想在多种客户端之间切换来切换去.那么就需要找一款支持多数据库的客户端工具了.如果你要连接多个关系型数据库,你就可以使用N ...

  2. 从零自学Hadoop(18):Hive的CLI和JDBC

    阅读目录 序 Hive CLI(old CLI) Beeline CLI(new CLI) JDBC Demo下载 系列索引 本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出 ...

  3. 为什么现在我最终推荐内存OLTP

    在今年的8月份,我写了篇文章,介绍了我还不推荐用户使用内存OLTP的各个理由.近日很多人告诉我,他们有一些性能的问题,并考虑使用内存OLTP来解决它们. 众所皆知,在SQL Server里内存OLTP ...

  4. CentOS 7 x64下Apache+MySQL(Mariadb)+PHP56的安装

    每次搭建新服务器,都要来来回回把这些包再装一下,来来回回搞了不下20遍了吧,原来都是凭经验,配置过程中重复入坑是难免的,故写此文做个备忘.虽然有像xampp这样的集成包,但是在生产环境的Linux发行 ...

  5. [LeetCode] Permutations 全排列

    Given a collection of numbers, return all possible permutations. For example,[1,2,3] have the follow ...

  6. 《MySQL 必知必会》读书总结

    这是 <MySQL 必知必会> 的读书总结.也是自己整理的常用操作的参考手册. 使用 MySQL 连接到 MySQL shell>mysql -u root -p Enter pas ...

  7. [翻译] Android是怎样绘制视图的

    原文:How Android Draws Views 当一个Activity获取到焦点的时候,它的布局就开始被绘制. 绘制的过程由Android framework处理.但布局层级的根节点必须由Act ...

  8. Jenkins + svn + maven 构建持续集成环境搭建

    Jenkins简介 Jenkins是一个开源软件项目,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括: ...

  9. 常用的shell脚本

    [root@WEB1-live sh]# cat licai_fabu.sh #!/bin/bash pid=` ps -ef | grep java | grep '8011' | awk '{pr ...

  10. Rabbitmq集群升级方案

    升级Rabbitmq 3.6.3版本至3.6.6版本,升级过程中的一些关键步骤记录 Step 1: 顺序关闭集群所有节点,这里注意最后一个关闭的节点必须保证为硬盘节点,而非RAM节点: centOS ...