Spring Flux 中 Request 与 HandlerMapping 关系的形成过程

一、前言

Spring Flux 中的核心 DispatcherHandler 的处理过程分为三步,其中首步就是通过 HandlerMapping 接口查找 Request 所对应的 Handler。本文就是通过阅读源码的方式,分析一下 HandlerMapping 接口的实现者之一——RequestMappingHandlerMapping 类,用于处理基于注解的路由策略,把所有用 @Controller 和 @RequestMapping 标记的类中的 Handler 识别出来,以便 DispatcherHandler 调用的。

HandlerMapping 接口的另外两种实现类:1、RouterFunctionMapping 用于函数式端点的路由;2、SimpleUrlHandlerMapping 用于显式注册的 URL 模式与 WebHandler 配对。

文章系列

二、对基于注解的路由控制器的抽象

Spring 中基于注解的控制器的使用方法大致如下:

1
2
3
4
5
6
7
@Controller
public class MyHandler{
@RequestMapping("/")
public String handlerMethod(){

}
}

在 Spring WebFlux 中,对上述使用方式进行了三层抽象模型。

  1. Mapping
    • 用户定义的基于 annotation 的映射关系
    • 该抽象对应的类是:org.springframework.web.reactive.result.method.RequestMappingInfo
    • 比如上述例子中的 @RequestMapping(“/“)所代表的映射关系
  2. Handler
    • 代表控制器的类
    • 该抽象对应的类是:java.lang.Class
    • 比如上述例子中的 MyHandler 类
  3. Method
    • 具体处理映射的方法
    • 该抽象对应的类是:java.lang.reflect.Method
    • 比如上述例子中的 String handlerMethod()方法

基于上述三层抽象模型,进而可以作一些组合。

  1. HandlerMethod
    • Handler 与 Method 的结合体,Handler(类)与 Method(方法)搭配后就成为一个可执行的单元了
  2. Mapping vs HandlerMethod
    • 把 Mapping 与 HandlerMethod 作为字典存起来,就可以根据请求中的关键信息(路径、头信息等)来匹配到 Mapping,再根据 Mapping 找到 HandlerMethod,然后执行 HandlerMethod,并传递随请求而来的参数。

理解了这个抽象模型后,接下来分析源码来理解 Spring WebFlux 如何处理请求与 Handler 之间的 Mapping 关系时,就非常容易了。

HandlerMapping 接口及其各实现类负责上述模型的构建与运作。

三、HandlerMapping 接口实现的设计模式

HandlerMapping 接口实现,采用了”模版方法”这种设计模式。

1 层:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware
^
|
2 层:AbstractHandlerMethodMapping implements InitializingBean
^
|
3 层:RequestMappingInfoHandlerMapping
^
|
4 层:RequestMappingHandlerMapping implements EmbeddedValueResolverAware

下面对各层的职责作简要说明:

  • 第 1 层主要实现了对外提供模型的接口
    • 即重载了 HandlerMapping 接口的”Mono getHandler(ServerWebExchange exchange) “方法,并定义了骨架代码。
  • 第 2 层有两个责任 —— 解析用户定义的 HandlerMethod + 实现对外提供模型接口实现所需的抽象方法
    • 通过实现了 InitializingBean 接口的”void afterPropertiesSet()”方法,解析用户定义的 Handler 和 Method。
    • 实现第 1 层对外提供模型接口实现所需的抽象方法:”Mono<?> getHandlerInternal(ServerWebExchange exchange)”
  • 第 3 层提供根据请求匹配 Mapping 模型实例的方法
  • 第 4 层实现一些高层次用到的抽象方法来创建具体的模型实例。

小结一下,就是 HandlerMapping 接口及其实现类,把用户定义的各 Controller 等,抽象为上述的 Mapping、Handler 及 Method 模型,并将 Mapping 与 HandlerMethod 作为字典关系存起来,还提供通过匹配请求来获得 HandlerMethod 的公共方法。

接下来的章节,将先分析解析用户定义的模型并缓存模型的过程,然后再分析一下匹配请求来获得 HandlerMethod 的公共方法的过程。

四、解析用户定义的模型并缓存模型的过程

4-1、实现 InitializingBean 接口

第 2 层 AbstractHandlerMethodMapping 抽象类中的一个重要方法——实现了 InitializingBean 接口的”void afterPropertiesSet()”方法,为 Spring WebFlux 带来了解析用户定义的模型并缓存模型的机会 —— Spring 容器初初始化完成该类的具体类的 Bean 后,将会回调这个方法。
在该方法中,实现获取用户定义的 Handler、Method、Mapping 以及缓存 Mapping 与 HandlerMethod 映射关系的功能。

1
2
3
4
5
6
7
8
@Override
public void afterPropertiesSet() {

initHandlerMethods();

// Total includes detected mappings + explicit registrations via registerMapping..
...
}

4-2、找到用户定义的 Handler

afterPropertiesSet 方法中主要是调用了 void initHandlerMethods()方法,具体如下:

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
protected void initHandlerMethods() {
// 获取 Spring 容器中所有 Bean 名字
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);

for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
// 获取 Bean 的类型
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}

// 如果获取到类型,并且类型是 Handler,则继续加载 Handler 方法。
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}

// 初始化后收尾工作
handlerMethodsInitialized(getHandlerMethods());
}

这儿首先获取 Spring 容器中所有 Bean 名字,然后循环处理每一个 Bean。如果 Bean 名称不是以 SCOPED_TARGET_NAME_PREFIX 常量开头,则获取 Bean 的类型。如果获取到类型,并且类型是 Handler,则继续加载 Handler 方法。

isHandler(beanType)调用,检查 Bean 的类型是否符合 handler 定义。
AbstractHandlerMethodMapping 抽象类中定义的抽象方法”boolean isHandler(Class<?> beanType)”,是由 RequestMappingHandlerMapping 类实现的。具体实现代码如下:

1
2
3
4
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

不难看出,对于 RequestMappingHandlerMapping 这个实现类来说,只有拥有 @Controller 或者 @RequestMapping 注解的类,才是 Handler。(言下之意对于其他实现类来说 Handler 的定义不同)。

具体 handler 的定义,在 HandlerMapping 各实现类来说是不同的,这也是 isHandler 抽象方法由具体实现类来实现的原因。

4-3、发现 Handler 的 Method

接下来我们要重点看一下”detectHandlerMethods(beanName);”这个方法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());

if (handlerType != null) {
// 将 handlerType 转换为用户类型(通常等同于被转换的类型,不过诸如 CGLIB 生成的子类会被转换为原始类型)
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 寻找目标类型 userType 中的 Methods,selectMethods 方法的第二个参数是 lambda 表达式,即感兴趣的方法的过滤规则
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
// 回调函数 metadatalookup 将通过 controller 定义的 mapping 与手动定义的 mapping 合并起来
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
if (logger.isTraceEnabled()) {
logger.trace("Mapped" + methods.size() + "handler method(s) for" + userType + ":" + methods);
}
methods.forEach((key, mapping) -> {
// 再次核查方法与类型是否匹配
Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
// 如果是满足要求的方法,则注册到全局的 MappingRegistry 实例里
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

首先将参数 handler(即外部传入的 BeanName 或者 BeanType)转换为 Class<?> 类型变量 handlerType。如果转换成功,再将 handlerType 转换为用户类型(通常等同于被转换的类型,不过诸如 CGLIB 生成的子类会被转换为原始类型)。接下来获取该用户类型里所有的方法(Method)。循环处理每个方法,如果是满足要求的方法,则注册到全局的 MappingRegistry 实例里。

4-4、解析 Mapping 信息

其中,以下代码片段有必要深入探究一下

1
2
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));

MethodIntrospector.selectMethods 方法的调用,将会把用 @RequestMapping 标记的方法筛选出来,并交给第二个参数所定义的 MetadataLookup 回调函数将通过 controller 定义的 mapping 与手动定义的 mapping 合并起来。
第二个参数是用 lambda 表达式传入的,表达式中将 method、userType 传给 getMappingForMethod(method, userType)方法。

getMappingForMethod 方法在高层次中是抽象方法,具体的是现在第 4 层 RequestMappingHandlerMapping 类中实现。在具体实现 getMappingForMethod 时,会调用到 RequestMappingHandlerMapping 类的下面这个方法。从该方法中,我们可以看到,首先会获得参数 element(即用户在 Controller 中定义的方法)的 RequestMapping 类型的类实例,然后构造代表 Mapping 抽象模型的 RequestmappingInfo 类型实例并返回。

1
2
3
4
5
6
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
truetrueRequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
truetrueRequestCondition<?> condition = (element instanceof Class ?
truetruetruetruegetCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
truetruereturn (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
true}

构造代表 Mapping 抽象模型的 RequestmappingInfo 类型实例,用的是 createRequestMappingInfo 方法,如下。可以看到 RequestMappingInfo 所需要的信息,包括 paths、methods、params、headers、consumers、produces、mappingName,即用户定义 @RequestMapping 注解时所设定的可能的参数,都被存在这儿了。拥有了这些信息,当请求来到时,RequestMappingInfo 就可以测试自身是否是处理该请求的人选之一了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

truetrueRequestMappingInfo.Builder builder = RequestMappingInfo
truetruetruetrue.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
truetruetruetrue.methods(requestMapping.method())
truetruetruetrue.params(requestMapping.params())
truetruetruetrue.headers(requestMapping.headers())
truetruetruetrue.consumes(requestMapping.consumes())
truetruetruetrue.produces(requestMapping.produces())
truetruetruetrue.mappingName(requestMapping.name());
truetrueif (customCondition != null) {
truetruetruebuilder.customCondition(customCondition);
truetrue}
truetruereturn builder.options(this.config).build();
true}

4-5、缓存 Mapping 与 HandlerMethod 关系

最后,registerHandlerMethod(handler, invocableMethod, mapping)调用将缓存 HandlerMethod,其中 mapping 参数是 RequestMappingInfo 类型的。。
内部调用的是 MappingRegistry 实例的 void register(T mapping, Object handler, Method method)方法,其中 T 是 RequestMappingInfo 类型。
MappingRegistry 类维护所有指向 Handler Methods 的映射,并暴露方法用于查找映射,同时提供并发控制。

1
2
3
4
5
6
7
8
9
10
11
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
......
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}

五、匹配请求来获得 HandlerMethod

AbstractHandlerMethodMapping 类的“Mono getHandlerInternal(ServerWebExchange exchange)”方法,具体实现了根据请求查找 HandlerMethod 的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   @Override
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
// 获取读锁
truethis.mappingRegistry.acquireReadLock();
truetry {
truetrueHandlerMethod handlerMethod;
truetruetry {
truetrue // 调用其它方法继续查找 HandlerMethod
truetruetruehandlerMethod = lookupHandlerMethod(exchange);
truetrue}
truetruecatch (Exception ex) {
truetruetruereturn Mono.error(ex);
truetrue}
truetrueif (handlerMethod != null) {
truetruetruehandlerMethod = handlerMethod.createWithResolvedBean();
truetrue}
truetruereturn Mono.justOrEmpty(handlerMethod);
true}
true// 释放读锁
truefinally {
truetruethis.mappingRegistry.releaseReadLock();
true}
}

handlerMethod = lookupHandlerMethod(exchange)调用,继续查找 HandlerMethod。我们继续看一下 HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定义。为方便阅读,我把注释也写在了代码里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception {
trueList<Match> matches = new ArrayList<>();
true// 查找所有满足请求的 Mapping,并放入列表 mathes
trueaddMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);

trueif (!matches.isEmpty()) {
true // 获取比较器 comparator
truetrueComparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
truetrue// 使用比较器将列表 matches 排序
truetruematches.sort(comparator);
truetrue// 将排在第 1 位的作为最佳匹配项
truetrueMatch bestMatch = matches.get(0);
truetrueif (matches.size() > 1) {
truetrue // 将排在第 2 位的作为次佳匹配项
truetruetrueMatch secondBestMatch = matches.get(1);
truetrue}
truetruehandleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange);
truetruereturn bestMatch.handlerMethod;
true}
trueelse {
truetruereturn handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange);
true}
}

六、总结

理解了 Spring WebFlux 在获取映射关系方面的抽象设计模型后,就很容易读懂代码,进而更加理解框架的具体处理方式,在使用框架时做到“知己知彼”。