热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringFlux中Request与HandlerMapping关系的形成过程

spring,flux,中,request,与,handlermapping,
一、前言

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

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

文章系列

  • 关于非阻塞IO:《从时间碎片角度理解阻塞IO模型及非阻塞模型》
  • 关于SpringFlux新手入门:《快速上手Spring Flux框架》
  • Spring Flux中Request与HandlerMapping关系的形成过程 本文
二、对基于注解的路由控制器的抽象

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

@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()方法

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

  4. HandlerMethod

    • Handler与Method的结合体,Handler(类)与Method(方法)搭配后就成为一个可执行的单元了
  5. 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
  • 第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映射关系的功能。

@Override public void afterPropertiesSet() { initHandlerMethods(); // Total includes detected mappings + explicit registrations via registerMapping.. ... }

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

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

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类实现的。具体实现代码如下:

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);"这个方法调用。

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 methods = MethodIntrospector.selectMethods(userType, //回调函数metadatalookup将通过controller定义的mapping与手动定义的mapping合并起来 (MethodIntrospector.MetadataLookup) 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信息

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

 Map methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup) 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类型实例并返回。

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition cOndition= (element instanceof Class ? getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element)); return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); }

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

protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) .methods(requestMapping.method()) .params(requestMapping.params()) .headers(requestMapping.headers()) .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name()); if (customCondition != null) { builder.customCondition(customCondition); } return builder.options(this.config).build(); }

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的映射,并暴露方法用于查找映射,同时提供并发控制。

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的逻辑。

 @Override public Mono getHandlerInternal(ServerWebExchange exchange) { //获取读锁 this.mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod; try { //调用其它方法继续查找HandlerMethod handlerMethod = lookupHandlerMethod(exchange); } catch (Exception ex) { return Mono.error(ex); } if (handlerMethod != null) { handlerMethod = handlerMethod.createWithResolvedBean(); } return Mono.justOrEmpty(handlerMethod); } //释放读锁 finally { this.mappingRegistry.releaseReadLock(); } }

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

 protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception { List matches = new ArrayList<>(); //查找所有满足请求的Mapping,并放入列表mathes addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange); if (!matches.isEmpty()) { //获取比较器comparator Comparator comparator = new MatchComparator(getMappingComparator(exchange)); //使用比较器将列表matches排序 matches.sort(comparator); //将排在第1位的作为最佳匹配项 Match bestMatch = matches.get(0); if (matches.size() > 1) { //将排在第2位的作为次佳匹配项 Match secOndBestMatch= matches.get(1); } handleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange); return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange); } }
六、总结

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


推荐阅读
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • JavaWeb中读取文件资源的路径问题及解决方法
    在JavaWeb开发中,读取文件资源的路径是一个常见的问题。本文介绍了使用绝对路径和相对路径两种方法来解决这个问题,并给出了相应的代码示例。同时,还讨论了使用绝对路径的优缺点,以及如何正确使用相对路径来读取文件。通过本文的学习,读者可以掌握在JavaWeb中正确找到和读取文件资源的方法。 ... [详细]
  • 本文介绍了ASP.NET Core MVC的入门及基础使用教程,根据微软的文档学习,建议阅读英文文档以便更好理解,微软的工具化使用方便且开发速度快。通过vs2017新建项目,可以创建一个基础的ASP.NET网站,也可以实现动态网站开发。ASP.NET MVC框架及其工具简化了开发过程,包括建立业务的数据模型和控制器等步骤。 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
  • SpringBoot简单日志配置
     在生产环境中,只打印error级别的错误,在测试环境中,可以调成debugapplication.properties文件##默认使用logbacklogging.level.r ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • React基础篇一 - JSX语法扩展与使用
    本文介绍了React基础篇一中的JSX语法扩展与使用。JSX是一种JavaScript的语法扩展,用于描述React中的用户界面。文章详细介绍了在JSX中使用表达式的方法,并给出了一个示例代码。最后,提到了JSX在编译后会被转化为普通的JavaScript对象。 ... [详细]
  • wpf+mvvm代码组织结构及实现方式
    本文介绍了wpf+mvvm代码组织结构的由来和实现方式。作者回顾了自己大学时期接触wpf开发和mvvm模式的经历,认为mvvm模式使得开发更加专注于业务且高效。与此同时,作者指出mvvm模式相较于mvc模式的优势。文章还提到了当没有mvvm时处理数据和UI交互的例子,以及前后端分离和组件化的概念。作者希望能够只关注原始数据结构,将数据交给UI自行改变,从而解放劳动力,避免加班。 ... [详细]
author-avatar
mobiledu2502890161
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有