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. java.lang.UnsatisfiedLinkError: %1 不是有效的 Win32 应用程序。

    JNA 调用 dll 库时,保错: ///////////////// 通过 JNA 引入 DLL 库 //////////// /** * ID_FprCap.dll 负责指纹的采集, 指纹仪的初始 ...

  2. [转]Writing Custom Middleware in ASP.NET Core 1.0

    本文转自:https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ One of the new ...

  3. Linux进程环境

    Linux下C程序都是main开始的,main函数的原型是: int main(int argc, char **argv) 其中argc是命令行参数的数目,argc是指向参数的各个指针所构成的数组. ...

  4. Webform:Session、Cookie对象的用法

    Session 优点:1.使用简单,不仅能传递简单数据类型,还能传递对象.  2.数据量大小是不限制的. 缺点:1.在Session变量存储大量的数据会消耗较多的服务器资源. 2.容易丢失. 使用方法 ...

  5. [LeetCode] Line Reflection 直线对称

    Given n points on a 2D plane, find if there is such a line parallel to y-axis that reflect the given ...

  6. [LeetCode] Minimum Height Trees 最小高度树

    For a undirected graph with tree characteristics, we can choose any node as the root. The result gra ...

  7. [LeetCode] Maximal Square 最大正方形

    Given a 2D binary matrix filled with 0's and 1's, find the largest square containing all 1's and ret ...

  8. 如何用Unity创建一个的简单的HoloLens 3D程序

    注:本文提到的代码示例下载地址>How to create a Hello World 3D holographic app with Unity 之前我们有讲过一次如何在HoloLens中创建 ...

  9. .Net4.0以上使用System.Data.Sqlite

    最近对Sqlite感兴趣,就尝试了一下用c#连接,我用的版本是vs2013,默认开发环境是.net4.5,,按照网上的教材,下载了System.Data.Sqlite,然后写了下面这个简单的测试代码, ...

  10. charing animation

    FHD : full high definition,1920 x 1080,全高清 vendor/mediatek/proprietary/bootable/bootloader/lk/dev/lo ...