SpringBoot之请求映射原理

前言

我们发出的请求,SpringMVC是如何精准定位到那个Controller以及具体方法?其实这都是 HandlerMapping 发挥的作用,这篇博文我们以 RequestMappingHandlerMapping 为例并结合源码一步步进行分析。

定义HandlerMapping

默认 HandlerMapping 主要定义在 EnableWebMvcConfiguration 和其祖父类 WebMvcConfigurationSupport

EnableWebMvcConfiguration
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {// Must be @Primary for MvcUriComponentsBuilder to workreturn super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,resourceUrlProvider);
}@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());return welcomePageHandlerMapping;
}
WebMvcConfigurationSupport
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();mapping.setOrder(2);PathMatchConfigurer pathConfig = getPathMatchConfigurer();if (pathConfig.getPatternParser() != null) {mapping.setPatternParser(pathConfig.getPatternParser());}else {mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());}mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));mapping.setCorsConfigurations(getCorsConfigurations());return mapping;
}@Bean
public RouterFunctionMapping routerFunctionMapping(@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {RouterFunctionMapping mapping = new RouterFunctionMapping();mapping.setOrder(3);mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));mapping.setCorsConfigurations(getCorsConfigurations());mapping.setMessageConverters(getMessageConverters());PathPatternParser patternParser = getPathMatchConfigurer().getPatternParser();if (patternParser != null) {mapping.setPatternParser(patternParser);}return mapping;
}@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {Assert.state(this.applicationContext != null, "No ApplicationContext set");Assert.state(this.servletContext != null, "No ServletContext set");PathMatchConfigurer pathConfig = getPathMatchConfigurer();ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());addResourceHandlers(registry);AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();if (handlerMapping == null) {return null;}if (pathConfig.getPatternParser() != null) {handlerMapping.setPatternParser(pathConfig.getPatternParser());}else {handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());}handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));handlerMapping.setCorsConfigurations(getCorsConfigurations());return handlerMapping;
}

PS:WebMvcConfigurationSupport 中定义的 HandlerMapping 不止上述三个,但是有效的只有三个 (SpringBoot 版本 2.6.13),有的 HandlerMapping 需要满足一定条件才生效。

初始化

initHandlerMappings
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {// 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);}}else {try {HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerMapping later.}}// Ensure we have at least one HandlerMapping, by registering// a default HandlerMapping if no other mappings are found.if (this.handlerMappings == null) {this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerMappings declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}for (HandlerMapping mapping : this.handlerMappings) {if (mapping.usesPathPatterns()) {this.parseRequestPath = true;break;}}
}
detectAllHandlerMappings 属性是否为 true (默认为true)
  • true:获取 BeanFactory 中类型为 HandlerMapping 的 beans
  • false : 获取 BeanFactory 中类型为 HandlerMapping,beanName为 handlerMapping 的 bean
如果 detectAllHandlerMappings 属性为 true,则会对查找到的 beans 进行排序,排序规则如下:
  1. HandlerMapping 是否继承 PriorityOrdered 接口,如果都继承 PriorityOrdered 接口,比较getOrder方法返回的值,值越小,优先级越高
  2. HandlerMapping 是否继承 Ordered 接口,如果都继承 Ordered接 口,比较 getOrder 方法返回的值,值越小,优先级越高
  3. HandlerMapping 所属class上是否存在 @Order 注解,如果存在,比较注解设置的值,值越小,优先级越高
  4. HandlerMapping 所属class上是否存在 @Priority 注解,如果存在,比较注解设置的值,值越小,优先级越高
如果从 BeanFactory 中未获取到相关 HandlerMapping,则使用默认 HandlerMapping

默认的 HandlerMapping 定义在 DispatcherServlet.properties 文件中

默认HandlerMapping为

  • BeanNameUrlHandlerMapping
  • RequestMappingHandlerMapping
  • RouterFunctionMapping

请求映射

DispatcherServlet#doDispatch

DispatcherServlet#getHandler

一共有五个 HandlerMapping (SpringBoot 版本 2.6.13,最新版本貌似有6个,大家自行验证一下),即我们上文所分析的在类EnableWebMvcConfiguration、WebMvcConfigurationSupport 中定义的 HandlerMapping

如果某个 HandlerMapping 的 getHandler 方法返回了一个有效的 HandlerExecutionChain,我们就可以认为这个 HandlerMapping 可以处理这个请求。接下里以 RequestMappingHandlerMapping 为例进行分析,大部分请求也都是由这个 HandlerMapping 处理的

PS : WelcomePageHandlerMapping 就是处理欢迎页的

RequestMappingHandlerMapping的实例化

RequestMappingHandlerMapping的类继承关系

通过上图,我们知道其祖父类(AbstractHandlerMethodMapping) 继承 InitializingBean 接口,继承 InitializingBean 接口的类会在bean的实例化过程中执行 afterPropertiesSet 方法

AbstractHandlerMethodMapping#afterPropertiesSet

获取所有类型是 Object 的 beans,并且 beanName 不以 scopedTarget. 开头

AbstractHandlerMethodMapping#processCandidateBean

根据 beanName 获取 beanType

AbstractHandlerMethodMapping#isHandler

如果 beanType 上存在 @Controller @RequestMapping 注解则进行处理

AbstractHandlerMethodMapping#detectHandlerMethods

主要有三个方法 selectMethods、getMappingForMethod、registerHandlerMethod

  1. selectMethods :遍历类中定义的方法以及接口实现方法(方法不能是合成的、桥接的,返回类型不能是Object ),如果某个方法执行接口函数(主体是 getMappingForMethod 方法)的返回值不为null,则将其放入一个类型为 Map<Method, T> 的 Map 中
  2. getMappingForMethod:如果方法存在 @RequestMapping (@GetMapping@PostMapping@PutMapping@DeleteMapping 等)注解,则构建一个 RequestMappingInfo 对象
  3. registerHandlerMethod : 遍历 selectMethods 方法的返回结果(Map<Method, T>),将其注册到 AbstractHandlerMethodMapping 的 mappingRegistry 属性中

PS : 经过测试,如果存在 @RequestMapping 注解,即使方法的描述符是 private,也可以接受请求

RequestMappingHandlerMapping的getHandler方法

AbstractHandlerMapping#getHandler

RequestMappingInfoHandlerMapping#getHandlerInternal

AbstractHandlerMethodMapping#getHandlerInternal

AbstractHandlerMethodMapping#lookupHandlerMethod

假设有一个UserController,明细由下方所示,我们来看一下这个 mappingRegistry 属性的结构

@RestController
public class UserController {@GetMapping("/user")public String getUser() {return "get user";}@PostMapping("/user")public String postUser() {return "post user";}@PutMapping("/user")public String putUser() {return "put user";}@DeleteMapping("/user")public String deleteUser() {return "delete user";}
}

URI 和具体方法的映射关系,都存储在 mappingRegistry 这个属性中

扩展:自定义HandlerMapping

创建 CustomHandlerMapping
public class CustomHandlerMapping implements HandlerMapping, PriorityOrdered {private ApplicationContext applicationContext;public CustomHandlerMapping(ApplicationContext applicationContext) {this.applicationContext = applicationContext;}@Overridepublic HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {String beanName = request.getParameter("beanName");String methodName = request.getParameter("method");if (StringUtils.isBlank(beanName) || StringUtils.isBlank(methodName) || !applicationContext.containsBean(beanName)) {return null;}Object object = applicationContext.getBean(beanName);Method method = null;Method[] declaredMethods = object.getClass().getDeclaredMethods();for (Method declaredMethod : declaredMethods) {if (declaredMethod.getName().equals(methodName)) {method = declaredMethod;break;}}if (method == null) {return null;}HandlerMethod handlerMethod = new HandlerMethod(object, method);return new HandlerExecutionChain(handlerMethod);}@Overridepublic int getOrder() {return 0;}
}

继承 PriorityOrdered 接口,让我们自定义的 HandlerMapping 优先级最高

创建 HandlerMappingConfig
@Configuration
public class HandlerMappingConfig implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}@Beanpublic CustomHandlerMapping customHandlerMapping() {return new CustomHandlerMapping(applicationContext);}
}
创建 CustomHandlerMappingController 
@Component("chmc")
public class CustomHandlerMappingController {@ResponseBodypublic String hello() {return "hello HandlerMapping! ";}
}
发送请求 localhost:8080?beanName=chmc&method=hello

自定义 handlerMapping 生效

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/349583.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 反射机制 -- Java 语言反射的概述、核心类与高级应用

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 010 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自…

通用大模型VS垂直大模型,你更青睐哪一方?

这里写目录标题 一、通用大模型简介二、垂直大模型简介三、通用大模型与垂直大模型的比较四、如何选择适合的模型五、通用大模型和垂直大模型的应用场景六、总结 近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;大模型的应用越来越广泛。无论是自然语言处理、计算机…

STL-常用容器

3.1.1. string基本概念 本质&#xff1a; string是C风格的字符串&#xff0c;char*是C语言风格的字符串string本质上是一个类 string和char*的区别&#xff1a; char*是一个指针string是一个类&#xff0c;类内部封装并负责管理char*&#xff0c;是一个char*型的容器 特点&a…

[Vue3:组件通信)子组件props接收和watch监听,emit发送父组件 (添加修改设置成绩,添加、删除选课记录)

文章目录 一&#xff1a;系统功能&#xff1a;设置成绩&#xff08;添加或修改&#xff09;交互逻辑&#xff1a;涉及页面 Page02.vue&#xff0c;ModalEdit.vue主页面Page.vue注入子页面&#xff0c;使用子页面标签属性主页面对子页面做通信&#xff0c;子页面ModalEdit接收参…

蓝卓为中小制造企业注入数字化转型活力

随着劳动力成本上升,原材料价格上涨,企业生产成本逐年增加&#xff0c;市场竞争越来越激烈&#xff0c;传统的中小制造企业面临着巨大的压力。 通过数字化转型应对环境的变化已成为行业共识&#xff0c;在数字化的进程中&#xff0c;中小企业首要考虑生存问题&#xff0c;不能…

用python脚本转换图片分辨率

一、使用说明 确定已经安装python&#xff0c;且版本3.6以上&#xff0c;可以用下面指令查看python版本&#xff1a;python --version 配置环境&#xff0c;第一次使用先配置环境&#xff0c;后面不需要 把要转换的图片放到"img"文件夹下 转换&#xff0c;结果保存…

Git代码冲突原理与三路合并算法

Git代码冲突原理 Git合并文件是以行为单位进行一行一行合并的&#xff0c;但是有些时候并不是两行内容不一样Git就会报冲突&#xff0c;这是因为Git会帮助我们进行分析得出哪个结果是我们所期望的最终结果。而这个分析依据就是三路合并算法。当然&#xff0c;三路合并算法并不…

使用Python和Matplotlib绘制复杂数学函数图像

本文介绍了如何使用Python编程语言和Matplotlib库来绘制复杂的数学函数图像。通过引入NumPy库的数学函数,我们可以处理包括指数函数在内的各种复杂表达式。本文详细讲解了如何设置中文字体以确保在图像中正确显示中文标题和标签,并提供了一个完整的代码示例,用户可以通过输入…

Python 越来越火爆

Python 越来越火爆 Python 在诞生之初&#xff0c;因为其功能不好&#xff0c;运转功率低&#xff0c;不支持多核&#xff0c;根本没有并发性可言&#xff0c;在计算功能不那么好的年代&#xff0c;一直没有火爆起来&#xff0c;甚至很多人根本不知道有这门语言。 随着时代的…

纯C实现的ymodem库,无额外依赖

本文目录 1、引言2、理论2.1 YMODEM协议的主要特点2.2 YMODEM的工作原理 3、代码3.1 main.cpp3.2 ymodem.c 3.3 ymodem.h 4、验证4.1 ymodem发送4.2 ymodem接收 5、移植说明 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我…

vue 渲染函数 h jsx

h 是什么 vue 提供的创建虚拟 DOM 节点 (vnode)的函数。 https://cn.vuejs.org/api/render-function.html#h jsx 是什么 JSX是 JavaScript XML&#xff08;HTML&#xff09;的缩写&#xff0c;表示在 JS 代码中书写 HTML 结构。简单理解就是&#xff1a; JSXjavascript xml&am…

带头+双向+循环链表的实现

目录 1. 链表1.1 带头双向循环链表 2. 链表的实现2.1 结构体2.2 初始化2.3 打印2.4 判断空不能删2.5 尾插2.6 头插2.7 尾删2.8 头删2.9 查找2.10 在pos之前插入2.11 删除pos位置的值2. 12 销毁2.13 创建节点 3. test主函数4. List.c文件5. List.h文件 1. 链表 1.1 带头双向循环…

AI大模型探索之路-实战篇:智能化IT领域搜索引擎之知乎网站数据获取(初步实践)

系列篇章&#x1f4a5; No.文章1AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎的构建与初步实践2AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎之GLM-4大模型技术的实践探索3AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎之知乎网站数据获…

Unity:Text-TextMeshPro 不显示中文

共计四步&#xff1a; 一、去C盘复制一份字体&#xff1a; C:\Windows\Fonts二、粘贴到你的项目里&#xff08;任意文件位置&#xff09;&#xff0c;得到“MSYH”&#xff1a; 三、右键字体文件&#xff0c;依次点击create–>TextMeshPro–>FontAsset&#xff1a; …

数据预处理——调整方差、标准化、归一化(Matlab、python)

对数据的预处理&#xff1a; (a)、调整数据的方差&#xff1b; (b)、标准化&#xff1a;将数据标准化为具有零均值和单位方差&#xff1b;&#xff08;均值方差归一化(Standardization)&#xff09; (c)、最值归一化&#xff0c;也称为离差标准化&#xff0c;是对原始数据的…

0. 云原生之基于乌班图远程开发

云原生专栏大纲 文章目录 安装乌班图配置静态IP重置root密码开启root远程登录开启远程SSH访问安装docker安装docker-compose安装Edge浏览器安装搜狗输入法安装TeamViewer安装虚拟显示器安装JDK安装maven安装vscodevscode插件安装VSCode配置maven、git、jdk、自动报错vscode快捷…

C++面向对象:多态性

多态性 1.概念 多态性是面向对象的程序设计的一个重要特征。在面向对象的方法中一般是这样表述多态的&#xff1a;向不同的对象发送同一个信息&#xff0c;不同的对象在接收时会产生不同的行为。也就是说&#xff0c;每个对象用自己的方式去响应共同的消息。 2.典例 下面这…

MPLS提高网络服务质量的原理

MPLS&#xff08;Multiprotocol Label Switching&#xff0c;多协议标签交换&#xff09;是一种网络技术&#xff0c;它能够提高网络的服务质量&#xff08;Quality of Service&#xff0c;QoS&#xff09;以及整体性能。MPLS通过以下几种方式来提升网络服务质量&#xff1a;标…

Unity引擎在UI上渲染粒子播放

大家好&#xff0c;我是阿赵。   在UI上面显示粒子特效&#xff0c;如果把粒子系统直接拖到Canvas里面&#xff0c;会存在很多问题&#xff0c;比如层级问题、裁剪问题等。这里分享一种用MaskableGraphic和UIVertex来显示粒子特效的方法。 一、 MaskableGraphic和UIVertex简…

r语言数据分析案例25-基于向量自回归模型的标准普尔 500 指数长期预测与机制分析

一、背景介绍 2007 年的全球经济危机深刻改变了世界经济格局&#xff0c;引发了一系列连锁反应&#xff0c;波及各大洲。经济增长停滞不前&#xff0c;甚至在某些情况下出现负增长&#xff0c;给出口导向型发展中国家带来了不确定性。实体经济受到的冲击尤为严重&#xff0c;生…