赞
踩
前面的章节我们讲了REST。本节,继续微服务专题的内容分享,共计16小节,分别是:
本节内容重点为:
HandlerMapping
、HandlerAdapter
以及 HandlerResultHandler
Spring Web MVC是基于Servlet API构建的原始Web框架,并从一开始就包含在Spring Framework中。正式名称“ Spring Web MVC”来自其源模块spring-webmvc的名称, 但它通常被称为“ Spring MVC”。
DispatcherServlet特殊bean 的委托来处理请求并提供适当的响应。“特殊bean”是指实现WebFlux框架协定的Spring管理的Object实例。这些通常带有内置合同,但是您可以自定义它们的属性,扩展或替换它们。
实际上SpringMVC的很多特性与webflux相同,比如下表是 Spring 官网上列出了通过检测到的特殊 bean DispatcherHandler:
Bean 的类型 | 说明 |
---|---|
HandlerMapping | 将请求与拦截器列表一起映射到处理程序,以 进行预处理和后期处理。映射基于某些标准,具体细节因HandlerMapping 实现而异。这两个主要的HandlerMapping 实现是RequestMappingHandlerMapping 支持带@RequestMapping 注释的方法,SimpleUrlHandlerMapping 并且维护URI路径模式向处理程序的显式注册。 |
HandlerAdapter | 帮助DispatcherServlet 调用映射到请求的处理程序,而不管实际如何调用该处理程序。例如,调用带注释的控制器需要解析注释。主要目的HandlerAdapter 是保护DispatcherServlet 这些细节。 |
HandlerExceptionResolver | 解决异常的策略,可能将异常映射到处理程序,HTML错误视图或其他。 |
ViewResolver | 解析从处理程序返回到实际的基于逻辑字符串的视图名称,View 以呈现给响应。 |
LocaleResolver, LocaleContextResolver | 解决Locale一个客户正在使用的并且可能是其时区的问题,以便能够提供国际化的视图。 |
ThemeResolver | 解决您的Web应用程序可以使用的主题,例如,提供个性化的布局。 |
MultipartResolver | 借助多部分解析库来解析多部分请求的抽象(例如,浏览器表单文件上传)。 |
FlashMapManager | 存储和检索“输入”和“输出” FlashMap,它们通常用于通过重定向将属性从一个请求传递到另一个请求。 |
除了上述处理类之外,我们再看类 HandlerInterceptor
的源码,共计三个方法 :
从源码上不难看出,分为 前置、后置处理、完成阶段(异常处理):
finally
效果。这和 java.util.concurrent.CompletableFuture#whenComplete
方法是不是有异曲同工之妙。org.springframework.web.servlet.HandlerMapping
接下来说一下 HandlerMapping
相关问题:
(上面截图的的spring版本为spring-5.0.7-RELEASE) 首先我们看HandlerMapping
的实现类 AbstractHandlerMapping
。
通常来讲 HandlerMapping 的实现类是包含 HandlerInterceptor
集合的。那么在 AbstractHandlerMapping
是否也存在呢,答案是肯定的,请见源码:
包含 HandlerInterceptor
集合 的意义主要在于:
既然是各司其职,就会引发顺序问题,谁先执行?谁后执行?
这里会引出一个概念------- 责任链模式
责任链通常有两种类型,即过滤类型和拦截类型。
Q:责任链和filter有什么区别呢?
A:实际上 HandlerInterceptor
就是采用返回值进行流程处理,而 Filter
采用 FilterChain
进行流程处理。 Filter
优先级天生比servlet高,但是 Filter
的最终节点 Servlet。Filter
通过filter.chain进入链条的下一个环节,在服务器启动阶段动态组合链条,符合责任链设计模式(动态调用,组合依赖于配置)。
回归主线,继续分析 HandlerMapping
的实现类 - AbstractHandlerMapping
:
在SpringMVC中,我们知道 DispatcherServlet
关联多个 HandlerMapping
,类似于笛卡尔积,即:
DispatcherServlet
: HandlerMapping
= 1 : NHandlerMapping
: HandlerInterceptor
= 1 : NQ: DispatcherServlet
作为 HandlerMapping
一种,那么问题来了,多个 HandlerMapping
谁能被 DispatcherServlet
选择?
这里我先猜想,上面截图的源码 AbstractHandlerMapping
实现了 Ordered
接口参考顺序,这是一种可以排序的可能,另外呢,如果 HandlerMapping
被请求规则匹配了是否可以呢?
猜想验证过程:
首先看一下 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法:
进去看 org.springframework.web.servlet.DispatcherServlet#getHandler:
这里很明显是通过循环将所有的handlerMappings 返回出去
所以为了看到handlerMappings 赋值的过程,我这里直接通过idea工具右键选中handlerMappings选择find usages菜单,查看读写的位置:
通过工具,发现handlerMappings的写入都集中在类 DispatcherServlet 上,所以直接跟进:
来到方法:org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
initHandlerMappings 方法,主要是初始化HandlerMappings的结果,这里首先将所有匹配的handlerMapping放入了Map<String,HandlerMapping> 。
注意 initHandlerMappings 方法中这段代码:
this.handlerMappings = new ArrayList<>(matchingBeans.values());
为什么不能将匹配的 handlerMappings直接赋值给当前this变量里呢,而是新创建一个集合再赋值?
我们看说明,返回值是:a collection view of the values contained in this map。注意是View,也就是说,你只能看看values(这里实际上是HandlerMappings),数据是不可变的。
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
接下来看看 sort 方法如何进行排序:
我们看 AnnotationAwareOrderComparator 的 API 发现如何排序的呢,有两种方式,分别是:
当实现了Ordered以后,可以通过 getOrder方法返回顺序,注意源码所示:将HIGHEST_PRECEDENCE赋值给Integer的最小值;将LOWEST_PRECEDENCE赋值给Integer的最大值。所以总结:在类Order的规则里,顺序越大,优先级越小,顺序越小,优先级越大!
@Order
注解在Bean上注入 @Order
注解,这里没有应用,略讲。
Tips:看源码就是要考虑很多细节性的问题,一个优秀的架构师,往往能够做到通用性好,并且思考的更深刻
回过头来,继续看主线内容:
前面提到: DispatcherServlet
可以关联多个 HandlerMapping
,HandlerMapping
也可以关联多个 HandlerInterceptor
,这种 1 对 N 的特性其实是有条件的!是需要经过筛选的,请看AbstractHandlerMapping
源码:
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 = obtainApplicationContext().getBean(handlerName); } //核心代码 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
此方法前面进行参数判断,尾部适配浏览器标准(跨域访问),重要看中间代码
直接看方法getHandlerExecutionChain:
注意这里:
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
此方法主要对HTTP请求的 URL 地址做处理,举个栗子:请求地址是 http://www.baidu.com/abc/def,那么 转化后剩下 /abc/def,其实就是相对路径。
接下来对于HandlerInterceptor 集合遍历,然后筛选,在本节开头就提到的HandlerInterceptor,它和MappedInterceptor有什么区别呢?
HandlerInterceptor可以匹配请求,而MappedInterceptor则不可以。
正因为HandlerInterceptor可以匹配请求,源码接着往下看,经过chain添加Interceptor才是被过滤的!
那么chain是怎么添加的Interceptor的呢?在回答这个问题之前,我们播放一个小插曲!
在前面的剧情里,AbstractHandlerMapping 重写了HandlerMapping
接口的 getHandler, 返回值是 HandlerExecutionChain
,现在我们看此返回值类:
这里面有两个集合:
为什么要用两个属性相同的集合来表示?
插曲播放完毕,让我们回到刚才的问题上,刚才说到的1对N问题,我们说是要经过筛选的!是通过chain去添加Interceptor的,那么怎么添加的呢?
我们将
chain.addInterceptor(mappedInterceptor.getInterceptor());
这段代码点击实现里看看:
private List<HandlerInterceptor> initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<>();
if (this.interceptors != null) {
// An interceptor array specified through the constructor
CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);
}
}
this.interceptors = null;
return this.interceptorList;
}
这里的实现是通过interceptors数组作为临时变量存储后merge到interceptorList集合里,然后清空interceptors数组。interceptors数组起到临时变量的作用。
问题来了,为什么要临时存储interceptor 集合呢?我们可以看看此集合在哪里被用到了便一目了然
老办法,对准临时变量interceptors就是操作find usages:
我们找到这里:
list是可以变化的,而数组是没法变化的,写两种集合,好处就在于:
最后我们再看类HandlerMapping 的 getHandler方法进行分析,getHandler的返回值是HandlerExecutionChain:
public class HandlerExecutionChain {
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
...
前面对于两个集合分析了一遍,那么看看这个Object类型的handler指的又是什么呢?
请参考本节开始的截图的HandlerMapping
类图:我们根据类图层级调用关系可以猜测到:
当一个request请求来临时,一定是通过handlerMapping
抽象实现类AbstractHandlerMapping
或者CompositeHandlerMapping
去处理请求返回handler的,那么CompositeHandlerMapping
实为透传,我们将重点放在AbstractHandlerMapping
类的getHandler:
我们看着一段代码:
Object handler = getHandlerInternal(request);
接着看getHandlerInternal:
后两个方法实为透传,我们分别看前两个实现类:
AbstractHandlerMethodMapping
AbstractHandlerMethodMapping
通过其实现类, RequestMappingInfoHandlerMapping
,再调用实现类 RequestMappingHandlerMapping
返回 HandlerMethod
。
AbstractUrlHandlerMapping
AbstractUrlHandlerMapping
获取 Handler(注意返回类型),在通过其实现类 SimpleUrlHandlerMapping
返回 Object
。
Tips: 通过这一小段分析,我们知道handler的类型是不固定的,通过
SimpleUrlHandlerMapping
处理后返回的handler类型是Object,而通过RequestMappingHandlerMapping
返回的的handelr类型是HandlerMethod
。
那么 HandlerMethod
与 执行方法有什么联系呢?
HandlerMethod
初始化过程如果当
@Controller
方法上面标注了@RequestMapping
,而@RequestMapping
主要可以标识URI。比如@RequestMapping(“/cache”)。所以我们就可以大胆猜测是通过RequestMappingHandlerMapping
处理的。
Q:那么源码上对于 HandlerMethod
与 执行方法是怎么处理的呢?
A:我们从 HandlerMethod
初始化的地方入手看 org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping#afterPropertiesSet:
afterPropertiesSet方法会在父类的Bean(RequestMappingHandlerMapping)初始化的时候调用。
我们看这里:
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
如果beanType 不为空,并且符合isHandler条件的,才能够进入里面的方法,所以我们先看看,什么是所谓的Handler呢?
就是说当前bean被标注为@Controller
或者@RequestMapping
注解的才会被扫描到。
那么
@RestController
以及@PostMapping
等注解会被扫描到么?答案是肯定的,还记得我们前面的小节讲到的注解的派生性么?可以简单的理解为子类与父类的关系,只是功能上等同哦,所以这里扫描@Controller
或者@RequestMapping
的父类是可以将子类扫描进去的,框架本身也更好的在注解层面实现了扩展。
然后看方法detectHandlerMethods,根据字面意思是,探测所有的HandlerMethods,所以进一步查看:
此方法首先求Bean,然后把Bean里的Method都执行一遍,注册HandlerMethod,进去看注册的过程:
透传参数,继续调用查看register方法:
由上述过程,可以总结一下HandlerMethod
初始化过程:
@Controller
或者 @RequestMapping
的 Bean@RequestMapping
方法Method
合并成 HandlerMethod
HandlerMethod
集合HandlerMethod
定位过程我们知道当一个Request请求进来时,要先经过 DispatchServlet 的doDispatch方法:
如何获取到的Handler呢?接着看源码的调用:
这里基本上相当于透传,接着往里看:
这不又是回到了最初的起点么?
我们通过前面的分析已经知道,实际从Request里解析的是:AbstractHandlerMapping
,而AbstractHandlerMapping
又是通过另外两个子类实现不同的返回值类型的Handler,这里我们前面已经提到。
这里以AbstractHandlerMehtodMapping
为切入点:我们看获取HandlerMethod
时这段代码:
基本上分为三个部分,前面判断如果没有MatchingMappings,则从所有的mapping集合里取出来:
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
接下来是匹配合适的Handler最后将其返回。
由上述过程,可以总结一下HandlerMethod
定位过程:
HandlerMethod
集合查找 @RequestMapping
定义的 URIHandlerMethod
HandlerMapping
和 HandlerAdapter
区别从DispatchServlet类的属性上我们看到,实际上1对多的关系有很多:
这里讲一下 HandlerMapping
和 HandlerAdapter
区别:
HandlerMapping
主要负责映射,通过一次 HTTP 请求找到对应(最佳匹配规则)的 HandlerMethod
以及多个 HandlerInterceptor
,而 HandlerInterceptor
也等同与 HandlerExecutionChain
,看一下此类的基本属性:
HandlerExecutionChain
这熟悉的三件套和我们之前分析AbstractHandlerMapping
不也是异曲同工么!
而 HandlerAdapter
主要负责 Handler 执行后处理,详细逻辑见org.springframework.web.servlet.DispatcherServlet#getHandler:
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { 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; }
HandlerMapping 的getHandler处理后返回HandlerExecutionChain。
HandlerInterceptor
: 没有匹配请求,而MappedInterceptor
: 能够匹配请求。
在老版本的页面渲染处理中, HTML页面渲染采用 JstlView
, JSON渲染则采用 MappingJackson2JsonView
。
并且我们看Controller 接口定义:
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
其方法返回值 ModelAndView
,方法参数 HttpServletRequest
和 HttpServletResponse
。
HandlerMapping
老实现:通常我们说
View
是来做页面渲染,同样的JstlView
也是对其做的实现,但是,实际场景中。@Controller
处理方法可能不返回ModelAndView
,这种情况下怎么办呢?
这就是SpringMVC新版本提出的特性:就是通过 HandlerMethod
对于 ModelAndView
进行适配。
到了Spring Web MVC 2.5+ 以后,则引入了适配器(Adapter)与HandlerMethod的概念。前面我们也分析了HandlerMethod流转过程,那么HandlerMethod作为形参有什么样的优势呢?
HandlerMethod
返回值:
ModelAndView
String
ResponseEntity
void
HandlerMethod
参数:
@RequestParam
@RequestHeader
@PathVariable
@RequestBody
Model
HandlerMapping
新实现:RequestMappingHandlerMapping
这些参数的扩展相对于老版本不是更加灵活了么!
org.springframework.web.reactive.HandlerMapping
对比参考 org.springframework.web.servlet.HandlerMapping
org.springframework.web.reactive.HandlerAdapter
对比参考 org.springframework.web.servlet.HandlerAdapter
org.springframework.web.reactive.DispatcherHandler
对比参考 org.springframework.web.servlet.DispatcherServlet
https://spring.io/projects/spring-cloud
更多架构知识,欢迎关注本套Java系列文章:Java架构师成长之路
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。