当前位置: 首页 > news >正文

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 生效

相关文章:

  • GIS之arcgis系列09:arcpy实现克里金差值
  • 【计算机毕业设计】258基于微信小程序的课堂点名系统
  • 开源项目-Docker部署学之思管理系统
  • [Android] Binder 里的 Service 和 Interface 分别是什么
  • 二维码扫描,没有生成,生成比较复杂
  • Web前端图形显示:深入探索与实用指南
  • 深入探索MySQL:性能调优与架构设计
  • Python数据分析与机器学习在医疗诊断中的应用
  • Flink Sql:四种Join方式详解(基于flink1.15官方文档)
  • 配置调整BGP网络的收敛速度方法
  • Flutter InAppWebView Unknown feature SUPPRESS_ERROR_PAGE
  • MySQL学习——在用Connector/NET处理BLOB数据
  • JavaScript-DOM
  • Java ORM 双雄:Mybatis vs Hibernate 的技术对决
  • 使用GPT/文心实现诗词作画
  • .pyc 想到的一些问题
  • Akka系列(七):Actor持久化之Akka persistence
  • JavaScript DOM 10 - 滚动
  • JS变量作用域
  • Js基础知识(一) - 变量
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Vultr 教程目录
  • 干货 | 以太坊Mist负责人教你建立无服务器应用
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 老板让我十分钟上手nx-admin
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 思维导图—你不知道的JavaScript中卷
  • 微信开源mars源码分析1—上层samples分析
  • 学习Vue.js的五个小例子
  • 自定义函数
  • const的用法,特别是用在函数前面与后面的区别
  • ​IAR全面支持国科环宇AS32X系列RISC-V车规MCU
  • #14vue3生成表单并跳转到外部地址的方式
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • #数学建模# 线性规划问题的Matlab求解
  • $(function(){})与(function($){....})(jQuery)的区别
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (vue)页面文件上传获取:action地址
  • (二)丶RabbitMQ的六大核心
  • (三)docker:Dockerfile构建容器运行jar包
  • (微服务实战)预付卡平台支付交易系统卡充值业务流程设计
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET MVC之AOP
  • .NET开源纪元:穿越封闭的迷雾,拥抱开放的星辰
  • .vue文件怎么使用_我在项目中是这样配置Vue的
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • @AliasFor注解
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • [ IO.File ] FileSystemWatcher
  • [].slice.call()将类数组转化为真正的数组
  • [5] CUDA线程调用与存储器架构
  • [Android] Amazon 的 android 音视频开发文档