讲解HandlerExecutionChain之前,先大致了解下SpringMVC的核心开发步骤:

  1. 在web.xml中部署DispaterServlet,并配置springmvc.xml等文件;
  2. 将映射文件请求到处理器HandlerMapping;
  3. HandlerMapping会把请求映射为HandlerExecutionChain类型的handler对象;
  4. 将handler对象作为参数传递给HandlerAdapter的实例化对象,调用其handler方法会生成一个ModelAndView对象;
  5. 通过ViewResolver视图解析器,将上一步骤中生成的ModelAndView解析为View;
  6. DispatcherServlet根据获取到View,将视图返回给用户。

本文分为两个部分进行讲解,第一部分分析Handler, 第二部分分析Interceptor

(一) Handler

首先可以明确HandlerExecutionChain与HanderMapping关系非常紧密,HandlerExecutionChain只能通过HanderMapping接口中的唯一方法来获得,HanderMapping接口定义如下:

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest; public interface HandlerMapping {
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
  
//该方法是HandlerMapping接口中的唯一方法,此方法可以利用用户请求request中的信息来生成HandlerExecutionChain对象
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }

  

HandlerMapping架构设计图如下:

可以看到HandlerMapping家族有两个分支,分别是AbstractUrlHandlerMappingAbstractHandlerMethodMapping,它们又统一继承于AbstractHandlerMapping

AbstractHandlerMapping是接口HandlerMapping的抽象实现,AbstractHandlerMapping抽象类中实现了部分方法提供给它的子类使用,它还覆了getHandler方法,源码如下:

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {   // AbstractUrlHandlerMapping和AbstractHandlerMethodMapping均对getHandlerInternal(request)进行了覆写
Object handler = getHandlerInternal(request);//该方法在本类中有定义,是一个protected型的抽象方法,
  //根据给定的request查找handler,如果没有找到handler,则返回一个null
  //如果经过上步没有获取到handler实例,则通过本类中setDefaultHandler(Object defaultHandler)设置默认handler,然后使用getDefaultHandler获得。
  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);
}

上面的getHandlerInternal在AbstractUrlHandlerMapping和AbstractHandlerMethodMapping中均有实现:

AbstractUrlHandlerMapping中的getHandlerInternal方法会根据用户请求信息中的URL查找handler:

 /** Look up a handler for the URL path of the given request.  -- 通过匹配URL,将URL与handler联系起来 */
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
... //略
}

AbstractHandlerMethodMapping中的getHandlerInternal方法则会根据用户request信息中提供的Method来查找handler:

/** Look up a handler method for the given request. -- 普遍用于@requestMaping,匹配内容将它的Method作为handler */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        ... //略
}

回到上文中的getHandler方法,它最终返回了一个HandlerExecutionChain,getHandlerExecutionChain(handler, request)方法属于 AbstractHandlerMapping 类的一个受保护类型方法,该方法定义如下:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 如果已经是HandlerExecutionChain,则直接使用,否则创建新的
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
//向chain中加入mappedInterceptor类型的拦截器
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
// 向chain中共加入拦截器
chain.addInterceptor(interceptor);
}
}
return chain;
}

  

简而言之就是:当用户请求到到DispaterServlet中后,配置的HandlerMapping会根据用户请求(也就是handler)会将它与所有的interceptors封装为HandlerExecutionChain对象,HandlerExecutionChain的作用在源码注释部分简要解释如下:

  Handler execution chain, consisting of handler object and any handler interceptors.

  Returned by HandlerMapping's HandlerMapping.getHandler method.

可知通过HandlerMapping实例对象的getHandler方法可以获得一个HandlerExecutionChain对象实例,该实例封装了一个handler处理对象和一些interceptors。HandlerExecutionChain类定义代码不长,其中所有的属性,方法如下:

(二)  Interceptor 拦截器

HandlerExecutionChain中介绍到了拦截器,那这个拦截器是何方圣神?下文来简单介绍一下。

首先引入拦截器。

上文中指出了HandlerMapping继承了AbstractHandlerMapping,AbstractHandlerMapping又继承于WebApplicationObjectSupport,进而继承了ApplicationObjectSupport。

 

ApplicationObjectSupport实现了ApplicationContextAware接口,在Spring容器中如果该bean类实现了ApplicationContextAware,那么通过容器获取这个bean时,void setApplicationContext(ApplicationContext context)方法将被调用。

setApplicationContext()方法调用了void initApplicationContext(),该方法在AbstractHandlerMapping中有实现,它主要的作用是加载拦截器以及初始化拦截器的一下配置,源代码如下:
 
@Override
protected void initApplicationContext() throws BeansException {
  extendInterceptors(this.interceptors);
  //探测容器中所有拦截器
  detectMappedInterceptors(this.mappedInterceptors);
  //初始配置这些拦截器
  initInterceptors();
}

 其中,initInterceptors()方法如下,主要作用是返回一个HandlerInterceptor对象,然后将这个对象放到 this.adaptedInterceptors 集合中(ps: adaptedInterceptors很重要哟)。

/**
* Initialize the specified interceptors, checking for {@link MappedInterceptor}s and
* adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor}s if necessary.
* @see #setInterceptors
* @see #adaptInterceptor
*/
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}

至此,了解了拦截器来龙,那接下来说一下拦截器的去脉。

拦截器的总接口 HandlerInterceptor 定义如下:

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

在spring-webmvc-4.3.1.RELEASE-sources.jar中,HandlerIntercetor共有13个实现类,如MappedInterceptor、webContentInterceptor、ThemeChangeInterceptor、HandlerIntercetorAdapter等,这里就不一一列出来了。

当客户端发送请求后,DispatcherServlet.doDispatch方法中会处理请求,下面贴出doDispatch(req, res)中与拦截器相关的代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...(前面代码略) try {
ModelAndView mv = null;
Exception dispatchException = null; try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // Determine handler for the current request.//获取根据请求获取handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
} // Determine handler adapter for the current request.//获取handler适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ...(略) // 拦截器执行拦截,对客户端请求响应requset进行拦截
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} // 核心逻辑,处理handler,返回ModerAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(processedRequest, mv);
// 拦截器执行拦截,对客户端响应response进行拦截
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
...(后面代码略)
}
}
} 

这里以mappedHandler.applyPreHandle(req, res)为例,简要分析一下doDispatch中的处理请求前拦截逻辑:

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取所有初始化的拦截器
HandlerInterceptor[] interceptors = getInterceptors();
// 遍历拦截器
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
// 遍历执行每个拦截器中的preHandle方法(即对某一个handler,要遍历执行所有的拦截器preHandle方法)
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
// 最终调用interceptor.afterCompletion(req, res, handler, ex)
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
} 

当然也可以自定义拦截器,只要实现HandlerInterceptor 接口中的preHandle、postHandle、afterCompletion方法就可以啦。

写在最后

其实看源码是最好的,也是最优效果的,本文只希望能在阅读源码的时候提供一点参考作用就够了。

浅探SpringMVC中HandlerExecutionChain之handler、interceptor的更多相关文章

  1. springmvc中的拦截器interceptor用法

    1.配置拦截器 在springMVC.xml配置文件增加: 1 <mvc:interceptors> 2 <!-- 日志拦截器 --> 3 <mvc:intercepto ...

  2. springMvc中实现拦截器Interceptor以及添加静态资源映射

    这个代码写了很久了,多久呢?2018年12-20号写的.... 废话不多说,简化一下,作为笔记. 注: public class springmvcConfig extends WebMvcConfi ...

  3. SpringMVC 中的Interceptor 拦截器

    1.配置拦截器 在springMVC.xml配置文件增加: <mvc:interceptors>  <!-- 日志拦截器 -->  <mvc:interceptor> ...

  4. SpringMVC之七:SpringMVC中使用Interceptor拦截器

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...

  5. SpringMVC中使用Interceptor拦截器

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...

  6. SpringMvc中Interceptor拦截器用法

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆等. 一. 使用场景 1 ...

  7. SpringMVC中的Interceptor拦截器及与Filter区别

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...

  8. [转]SpringMVC中使用Interceptor拦截器

    SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...

  9. SpringMVC中使用Interceptor拦截器顺序

    一.简介 SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验 证,或者是来判断用户是否登陆,或者是像1 ...

随机推荐

  1. 201621123010《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图或相关笔记,对面向对象思想进行一个总结. 2. 书面作业 1. clone方法 1.1 在 ...

  2. Winform开发之窗体显示、关闭与资源释放

    Winform的窗体涉及到一般窗体(单文档窗体).MDI窗体.窗体之间的关系等,那么如果调用打开新窗体.如何关闭窗体.窗体资源的释放等都关系到软件运行的效率,本文一一介绍 1.窗体的显示 从一个窗体打 ...

  3. JQ深度手记、源码分析

    1.$.extend() 对象继承操作.浅拷贝操作.深拷贝操作(第一个参数:true) var a = { name:'lisan' }; var b = {}; $.extend(b, a); // ...

  4. Oracle:"ORA-00942: 表或视图不存在"

    情景 项目中使用Powerdesigner设计数据结构,在Powerdesigner中数据表和字段都区分了大小写,并生成了Oracle表,在执行Sql脚本时遇到以下问题:“ORA-00942: 表或视 ...

  5. yum安装apache及问题解决

    一.检查服务器上是否已经安装了apache apache在linux系统里的名字是httpd,执行以下命令,如果有返回的信息,则会显示已经安装的软件.如果没有则不会显示其它的信息. rpm -qa h ...

  6. Jmeter二次开发之代码环境搭建(QQ交流群:577439379)

    一.创建项目 1. 分别下载apache3.1 binaries和source两个压缩包,前者为release版本,后者为jmeter最新的源码,下载地址:http://jmeter.apache.o ...

  7. Windows Redis安装,Java操作Redis

    一.Redis 的安装 1.Redis 下载 Windows 版本下载:https://github.com/dmajkic/redis/downloads 2.解压到 C:\redis-2.4.5- ...

  8. There is no Action mapped for namespace / and action name login. - [unknown location]

    (自己在浏览器中,直接进入项目的根目录,即 http://localhost:8080/ssh/  时便报错,web.xml文件已经配置了 欢迎页面 <welcome-file-list> ...

  9. ACM-Teleportation

    我的代码: #include <bits/stdc++.h> using namespace std; int main() { int a,b,x,y; cin>>a> ...

  10. java web 程序---内置对象application的log方法的使用

    application的主要方法里,有log方法,是日志文件里可以查看到信息的. 当老师写好代码后,他发现在tomact里的log目录下找不到信息,原因是:我们用myeclipse这个客户端软件,应该 ...