HandlerMapping和HandlerAdapter

这个两个组件应该算是spring mvc中最重要的几个组件之一了,当一个请求到达DispatcherSerlvet后,spring mvc就全靠这各两个组件定位并调用我们定义的Controller函数。是的,他们的功能就分别对应了“定位”和“调用”。

HandlerMapping

先看看该接口的申明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
publicinterface HandlerMapping {
  // ...
  // 其他常量定义
  /**
  * Return a handler and any interceptors for this request. The choice may be made
  * on request URL, session state, or any factor the implementing class chooses.
  * <p>The returned HandlerExecutionChain contains a handler Object, rather than
  * even a tag interface, so that handlers are not constrained in any way.
  * For example, a HandlerAdapter could be written to allow another framework's
  * handler objects to be used.
  * <p>Returns {@code null} if no match was found. This is not an error.
  * The DispatcherServlet will query all registered HandlerMapping beans to find
  * a match, and only decide there is an error if none can find a handler.
  * @param request current HTTP request
  * @return a HandlerExecutionChain instance containing handler object and
  * any interceptors, or {@code null} if no mapping found
  * @throws Exception if there is an internal error
  */
  HandlerExecutionChain getHandler(HttpServletRequest request) throwsException;
}

实际干事的就只有getHandler一个方法,根据http请求确定将要被执行的执行链HandlerExecutionChain。一个HandlerExecutionChain就是由目标handler和一组HandlerInterceptor组成。但是需要注意的是,HandlerExecutionChain并不负责真正的执行动作,它也不知道如何去执行目标handler,而仅仅是一个保存这些对象的容器罢了。

目标的handler是Object类型,换句话说spring没有提供任何接口来限定,可以是任何类型。因此真正的执行动作会发生在HandlerAdpater中,也就是说如果每个HandlerMapping(不管是spring提供的还是你自己写的)都需要有对应的HandlerAdpater,当然不一定是一一对应有些是可以复用的。

如何定义HandlerInterceptor

不像目标handler,handler执行链上的拦截器是有限定类型的,也就是上面提到的HandlerInterceptor。那么如何配置这些HandlerInterceptor呢?

首先需要明确的是interceptor最终都会被配置到容器中使用的HandlerMapping组件中去,因为HandlerMapping会产生HandlerExecutionChain,需要将所有的interceptor一并设置到返回的HandlerExecutionChain中。那么最直接的方式就是在定义HandlerMapping的地方将需要的interceptor直接注入到对应的HandlerMapping类中,实际上该字段是声明在AbstractHandlerMapping中,因此所有的HandlerMapping最好直接从AbstractHandlerMapping抽象类上继承,而不要直接实现HandlerMapping接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<beans>
    <bean id="handlerMapping"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMap
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
      <property name="interceptors">
                    <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>
    <bean id="officeHoursInterceptor"
          class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime"value="9"/>
        <property name="closingTime"value="18"/>
    </bean>
<beans>

实际上在spring中HandlerInterceptor有两类,一类是名符其实的实现了HandlerInterceptor接口的类;另外一类是MappedInterceptor,顾名思义它除了HandlerInterceptor的功能外还有了path match的能力,实际上它就是包含了一个真正的HandlerInterceptor外加一些路径匹配表达式。它的作用除了能够让spring调用其中包含的HandlerInterceptor之外,还具有路径匹配的功能,也就是说会告诉spring只有当指定request的请求路径复合要求的时候才会调用该interceptor。

OK,在回到配置HandlerInterceptor的第二种方法,就是使用<mvc:interceptors/>标签,如下:

1
2
3
4
5
6
7
8
<mvc:interceptors>
  <beanclass="my.MyInterceptor"/>
  <ref bean="interceptorRef"/>
  <mvc:interceptor>
      <mvc:mapping path="/interceptor/*"/>
      <beanclass="my.MyInterceptor1"/>
  </mvc:interceptor>
</mvc:interceptors>

这个例子就定义了三个interceptor,分别通过bean, ref, interceptor子元素。其中bean和ref定义的interceptor会匹配任何request(因为没有指定mapping path);使用interceptor子元素就可以指定mapping path了,那么它所表示的HandlerInterceptor就会根据request path来决定是否要执行。这些标签都会被转变为前面提到的MappedInterceptor

前面说了HandlerInterceptor会最终被应用到HandlerMapping中,那通过xml配置的interceptor呢?实际上他们会被同时自动配置到spring容器中定义的所有HandlerMapping中,这也是最合乎情理的,因为你并不需要同时考虑你根据path所配置的interceptor到底应该作用到那个HandlerMapping中。相反,所有的HandlerMapping在拥有了这些MappedInterceptor后,在准备HandlerExecutionChain时就会根据当前的request path来决定要把哪些MappedInterceptor放进去,当然所有直接定义的HandlerInterceptor都会被放入chain中。

那么spring是怎么把这些MappedInterceptor放入到HandlerMapping中的呢?实际上spring仅仅是把他们定义到容器中,在HandlerMapping初始化的时候通过调用AbstractHandlerMapping.detectMappedInterceptors方法来自动发现所有的MappedInterceptor,并做一些必要的初始化配置。

另外一点,如果你使用了<mvc:annotation-driven/>的话,默认是会添加一个MappedInterceptor到容器中,这个interceptor是ConversionServiceExposingInterceptor,它会把<mvc:annotation-driven/>检测或者创建的conversionService添加到HttpServletRequest的一个属性中,以便整个http request处理流程可以随时享用这个conversionService。因为并不是所有的组件都有享受spring ioc的能力,比如jsp tag,因此放在HttpServletRequest会比较方便。

HandlerAdapter

如何配置

HandlerAdapter是spring mvc中的独立组件,因此和其他核心组件一样可以通过一些三种方法获得:

  1. DispatcherServlet.peroperties默认提供
  2. <mvc:annotation-driven/>提供
  3. 自己配置在spring配置文件中

注意,2和3会disable掉1,但是2和3又会同时起作用。

应用流程

还是先看接口声明吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
publicinterface HandlerAdapter {
  /**
  * Given a handler instance, return whether or not this {@code HandlerAdapter}
  * can support it. Typical HandlerAdapters will base the decision on the handler
  * type. HandlerAdapters will usually only support one handler type each.
  * <p>A typical implementation:
  * <p>{@code
  * return (handler instanceof MyHandler);
  * }
  * @param handler handler object to check
  * @return whether or not this object can use the given handler
  */
  booleansupports(Object handler);
  /**
  * Use the given handler to handle this request.
  * The workflow that is required may vary widely.
  * @param request current HTTP request
  * @param response current HTTP response
  * @param handler handler to use. This object must have previously been passed
  * to the {@code supports} method of this interface, which must have
  * returned {@code true}.
  * @throws Exception in case of errors
  * @return ModelAndView object with the name of the view and the required
  * model data, or {@code null} if the request has been handled directly
  */
  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throwsException;
  /**
  * Same contract as for HttpServlet's {@code getLastModified} method.
  * Can simply return -1 if there's no support in the handler class.
  * @param request current HTTP request
  * @param handler handler to use
  * @return the lastModified value for the given handler
  * @see javax.servlet.http.HttpServlet#getLastModified
  * @see org.springframework.web.servlet.mvc.LastModified#getLastModified
  */
  longgetLastModified(HttpServletRequest request, Object handler);
}

DispatherServlet在通过前面的HandlerMapping获得了当前请求的HandlerExecutionChain之后,就会哪些chain里面定义的目标handler遍历所有配置好的HandlerAdapter,并调用supports方法询问不同的adapter是否可以处理,如果可以就进入处理流程,如下:

1
2
3
4
5
6
7
8
9
10
11
12
protectedHandlerAdapter getHandlerAdapter(Object handler) throwsServletException {
  for(HandlerAdapter ha : this.handlerAdapters) {
      if(logger.isTraceEnabled()) {
          logger.trace("Testing handler adapter [" + ha + "]");
      }
      if(ha.supports(handler)) {
          returnha;
      }
  }
  thrownew ServletException("No adapter for handler [" + handler +
          "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

处理流程如下:

  1. 如果是GET或者HEAD请求,调用HandlerAdapter.getLastModified方法看看目标Controller方法在对于该请求有没有可用的lastModified逻辑,如果有的话就使用ServletWebRequest.checkNotModified逻辑判断当前lastModfied值和http header的上次缓存值,如果还没有过期就设置304头并且返回并结束整个请求流程。否则继续。
  2. 应用preHandle方法,调用所有的HandlerInterceptor.preHandle方法
  3. 调用HandlerAdapter.handle方法进行目标handler的调用(调用controller),得到ModelAndView返回值
  4. 应用interceptor.postHandle方法。
  5. 最后根据handle返回值的请求调用DispatcherServlet.processDispatchResult方法来根据返回值类型处理成最终的http response。

一个栗子

逻辑就是这么简单,没有什么好多说的,因为就像前面说的不同的HandlerAdapter是需要配合不同的HandlerMapping产生的目标handler,没有固定的规律和模式。就拿SimpleControllerHandlerAdapter这个例子来说明下把。可以和它配对的HandlerMapping有ControllerBeanNameHandlerMappingControllerClassNameHandlerMapping,或者说从AbstractControllerUrlHandlerMapping继承下来的类。

先看看SimpleControllerHandlerAdapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
publicclass SimpleControllerHandlerAdapter implementsHandlerAdapter {
  @Override
  publicboolean supports(Object handler) {
      return(handler instanceofController);
  }
  @Override
  publicModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
          throwsException {
      return((Controller) handler).handleRequest(request, response);
  }
  @Override
  publiclong getLastModified(HttpServletRequest request, Object handler) {
      if(handler instanceofLastModified) {
          return((LastModified) handler).getLastModified(request);
      }
      return-1L;
  }
}

可以得出以下简单的结论:

  1. 它只能处理目标handler是Controller类型(实现了Controller接口)的调用
  2. 对于lastModified特性,如果目标handler(从1可知肯定是一个Controller类型)也实现了LastModifed接口,那么就调用该接口的getLastMofied函数来得到lastMofiy值,否则返回-1表示不支持。
  3. 调用过程非常简单,就是调用目标Controller的handleRequest方法。

从中我们可以断定和它配合的HandlerMapping返回的目标handler必须是Controller类型。好吧,我们来看看ControllerBeanNameHandlerMappingControllerClassNameHandlerMapping是干什么的。他们两个实际上是非常相似的,共同的父类都会扫描容器中所有定义的bean,如果该bean是Controller类型,那么就交给这两个不同的子类做处理来决定如何将这个Controller加入到mapping中。

  1. 对于ControllerBeanNameHandlerMapping,它会把这个bean的名字及其alias作为request path
  2. 对于ControllerClassNameHandlerMapping,它会把这个bean的类名作为request path,比如HelloController对应为”/hello”。

那么在收到请求后,这两个HandlerMapping会根据request path匹配已经保存的mapping数据,如果找到匹配的就会将之前存好的这个bean,也就是这个Controller对象当做目标handler返回出去。在后面的调用流程中自然就可以被SimpleControllerHandlerAdapter处理了。

当然,一个请求来了具体被那个HandlerMapping处理要看不同HandlerMapping的处理能力,还处理顺序,自己不能处理的旧交由下一个处理,其顺序是HandlerMapping的order值确定的。

这仅仅是一个例子,目前Controller类已经不推荐使用了,更多的请使用annotation的方法,当然其对应的处理组件是RequestMappingHandlerMapping和RequestMappingHandlerAdapter。

原文出处: shenzhang

from: http://www.importnew.com/22188.html

Spring MVC中的二三事相关推荐

  1. 在Spring MVC中使用Apache Shiro安全框架

    我们在这里将对一个集成了Spring MVC+Hibernate+Apache Shiro的项目进行了一个简单说明.这个项目将展示如何在Spring MVC 中使用Apache Shiro来构建我们的 ...

  2. Spring MVC中的视图解析ViewResolver

    http://blog.csdn.net/prince2270/article/details/5891085 在Spring MVC中,当Controller将请求处理结果放入到ModelAndVi ...

  3. Spring mvc 中使用 kaptcha 验证码

    2019独角兽企业重金招聘Python工程师标准>>> 生成验证码的方式有很多,个人认为较为灵活方便的是Kaptcha ,他是基于SimpleCaptcha的开源项目.使用Kaptc ...

  4. Spring MVC中静态资源的处理

    Spring MVC中静态资源的处理 REST风格的资源URL中不带后后缀,比如 /blog/tom -> 用户tom的blog资源 /order/1234 -> 订单号为1234的订单资 ...

  5. Spring MVC中获取Request的方法及分析

    一.概述 在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端IP地址.请求的URL.header中的属性(如cookie.授权信息).body中的数 ...

  6. 彻底解决Spring mvc中时间的转换和序列化等问题

    彻底解决Spring mvc中时间的转换和序列化等问题 参考文章: (1)彻底解决Spring mvc中时间的转换和序列化等问题 (2)https://www.cnblogs.com/childkin ...

  7. spring mvc中的@propertysource

    在spring mvc中,在配置文件中的东西,可以在java代码中通过注解进行读取了: @PropertySource  在spring 3.1中开始引入 比如有配置文件 config.propert ...

  8. spring_在Spring MVC中使用多个属性文件

    spring 每个人都听说过将单个Web应用程序组合成一个大型Web应用程序的门户. 门户软件的工作原理类似于mashup -来自多个来源的内容是在单个服务中获取的,大部分都显示在单个网页中. 门户软 ...

  9. Spring MVC中处理Request和Response的策略

    前沿技术早知道,弯道超车有希望 积累超车资本,从关注DD开始 作者:码农小胖哥, 图文编辑:xj 来源:https://mp.weixin.qq.com/s/3eFygsiVl8dC2nRy8_8n5 ...

最新文章

  1. [PyTorch]一个非常好的抢救outofmemory的方法
  2. java生成pdf工具类,聪明人已经收藏了!
  3. python求散点曲线下方面积
  4. 深入解读华为云细粒度文本情感分析及其应用
  5. 去百度/阿里/腾讯…做测试的,都是什么样的人?
  6. vi是linux文本界面编辑器吗,LINUX初学之文本编辑器(vi ,vim)
  7. 阅读替换净化规则_强力推荐一个开源阅读软件
  8. python统计数组元素个数_统计二维数组里元素的个数
  9. ajax提交与上传文件同步
  10. UVA - 1588 Kickdown
  11. Spring MVC 基于URL的映射规则(注解版)
  12. Grails 技巧 - View 篇
  13. MacOS 10.14.5单双面打印设置
  14. 易语言API HooK CreateThread创建线程
  15. 利用Python制作本地Excel的查询与生成的程序
  16. 枣庄高防php主机,枣庄机房_枣庄联通机房_枣庄BGP高防机房
  17. 公司要收我的毕业证书,这合法吗
  18. seting the network namespace failed: Invalid argument
  19. Word段落前面有黑点 Word段落黑点怎么去掉
  20. nodejs学习笔记--Unexpected end of JSON input while parsing near ‘...“解决方法

热门文章

  1. 【采用】解读消金业务风控模型的6个层级
  2. 谷歌发布TensorFlow 1.4与TensorFlow Lattice:利用先验知识提升模型准确度 搜狐科技 10-12 15:29 选自:Google Research Blog 参与:李泽南、
  3. 比特币早期投资家:没有人能够阻止其发展 TechWeb 09-27 09:10 凤凰科技讯 据CNBC网站北京时间9月27日报道,风险投资家、“Social+Capital”基金创始人Chamath
  4. WhatsApp CEO向FreeBSD捐赠一百万美金
  5. 2014年各种编程语言的薪资和市场需求
  6. mysql对数据库进行备份吗_怎么对MySQL数据库进行备份与恢复
  7. Chromium:安装depot_tools及获取Chromium源代码
  8. Spring Cloud【Finchley】实战-02订单微服务
  9. Spring Boot2.x-12 Spring Boot2.1.2中Filter和Interceptor 的使用
  10. 个人博客系统的设计与实现_一个 Go 开发的快速、简洁、美观、前后端分离的个人博客系统...