当前位置:   article > 正文

SpringBoot之请求映射原理

SpringBoot之请求映射原理

前言

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

定义HandlerMapping

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

EnableWebMvcConfiguration
  1. @Bean
  2. @Primary
  3. @Override
  4. public RequestMappingHandlerMapping requestMappingHandlerMapping(
  5. @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  6. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  7. @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
  8. // Must be @Primary for MvcUriComponentsBuilder to work
  9. return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
  10. resourceUrlProvider);
  11. }
  12. @Bean
  13. public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
  14. FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
  15. WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
  16. new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
  17. this.mvcProperties.getStaticPathPattern());
  18. welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
  19. welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
  20. return welcomePageHandlerMapping;
  21. }
WebMvcConfigurationSupport
  1. @Bean
  2. public BeanNameUrlHandlerMapping beanNameHandlerMapping(
  3. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  4. @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
  5. BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
  6. mapping.setOrder(2);
  7. PathMatchConfigurer pathConfig = getPathMatchConfigurer();
  8. if (pathConfig.getPatternParser() != null) {
  9. mapping.setPatternParser(pathConfig.getPatternParser());
  10. }
  11. else {
  12. mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
  13. mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
  14. }
  15. mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
  16. mapping.setCorsConfigurations(getCorsConfigurations());
  17. return mapping;
  18. }
  19. @Bean
  20. public RouterFunctionMapping routerFunctionMapping(
  21. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  22. @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
  23. RouterFunctionMapping mapping = new RouterFunctionMapping();
  24. mapping.setOrder(3);
  25. mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
  26. mapping.setCorsConfigurations(getCorsConfigurations());
  27. mapping.setMessageConverters(getMessageConverters());
  28. PathPatternParser patternParser = getPathMatchConfigurer().getPatternParser();
  29. if (patternParser != null) {
  30. mapping.setPatternParser(patternParser);
  31. }
  32. return mapping;
  33. }
  34. @Bean
  35. @Nullable
  36. public HandlerMapping resourceHandlerMapping(
  37. @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  38. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  39. @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
  40. Assert.state(this.applicationContext != null, "No ApplicationContext set");
  41. Assert.state(this.servletContext != null, "No ServletContext set");
  42. PathMatchConfigurer pathConfig = getPathMatchConfigurer();
  43. ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
  44. this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
  45. addResourceHandlers(registry);
  46. AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
  47. if (handlerMapping == null) {
  48. return null;
  49. }
  50. if (pathConfig.getPatternParser() != null) {
  51. handlerMapping.setPatternParser(pathConfig.getPatternParser());
  52. }
  53. else {
  54. handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
  55. handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
  56. }
  57. handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
  58. handlerMapping.setCorsConfigurations(getCorsConfigurations());
  59. return handlerMapping;
  60. }

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

初始化

initHandlerMappings
  1. private void initHandlerMappings(ApplicationContext context) {
  2. this.handlerMappings = null;
  3. if (this.detectAllHandlerMappings) {
  4. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
  5. Map<String, HandlerMapping> matchingBeans =
  6. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
  7. if (!matchingBeans.isEmpty()) {
  8. this.handlerMappings = new ArrayList<>(matchingBeans.values());
  9. // We keep HandlerMappings in sorted order.
  10. AnnotationAwareOrderComparator.sort(this.handlerMappings);
  11. }
  12. }
  13. else {
  14. try {
  15. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  16. this.handlerMappings = Collections.singletonList(hm);
  17. }
  18. catch (NoSuchBeanDefinitionException ex) {
  19. // Ignore, we'll add a default HandlerMapping later.
  20. }
  21. }
  22. // Ensure we have at least one HandlerMapping, by registering
  23. // a default HandlerMapping if no other mappings are found.
  24. if (this.handlerMappings == null) {
  25. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
  26. if (logger.isTraceEnabled()) {
  27. logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
  28. "': using default strategies from DispatcherServlet.properties");
  29. }
  30. }
  31. for (HandlerMapping mapping : this.handlerMappings) {
  32. if (mapping.usesPathPatterns()) {
  33. this.parseRequestPath = true;
  34. break;
  35. }
  36. }
  37. }
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 属性的结构

  1. @RestController
  2. public class UserController {
  3. @GetMapping("/user")
  4. public String getUser() {
  5. return "get user";
  6. }
  7. @PostMapping("/user")
  8. public String postUser() {
  9. return "post user";
  10. }
  11. @PutMapping("/user")
  12. public String putUser() {
  13. return "put user";
  14. }
  15. @DeleteMapping("/user")
  16. public String deleteUser() {
  17. return "delete user";
  18. }
  19. }

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

扩展:自定义HandlerMapping

创建 CustomHandlerMapping
  1. public class CustomHandlerMapping implements HandlerMapping, PriorityOrdered {
  2. private ApplicationContext applicationContext;
  3. public CustomHandlerMapping(ApplicationContext applicationContext) {
  4. this.applicationContext = applicationContext;
  5. }
  6. @Override
  7. public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  8. String beanName = request.getParameter("beanName");
  9. String methodName = request.getParameter("method");
  10. if (StringUtils.isBlank(beanName) || StringUtils.isBlank(methodName) || !applicationContext.containsBean(beanName)) {
  11. return null;
  12. }
  13. Object object = applicationContext.getBean(beanName);
  14. Method method = null;
  15. Method[] declaredMethods = object.getClass().getDeclaredMethods();
  16. for (Method declaredMethod : declaredMethods) {
  17. if (declaredMethod.getName().equals(methodName)) {
  18. method = declaredMethod;
  19. break;
  20. }
  21. }
  22. if (method == null) {
  23. return null;
  24. }
  25. HandlerMethod handlerMethod = new HandlerMethod(object, method);
  26. return new HandlerExecutionChain(handlerMethod);
  27. }
  28. @Override
  29. public int getOrder() {
  30. return 0;
  31. }
  32. }

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

创建 HandlerMappingConfig
  1. @Configuration
  2. public class HandlerMappingConfig implements ApplicationContextAware {
  3. private ApplicationContext applicationContext;
  4. @Override
  5. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  6. this.applicationContext = applicationContext;
  7. }
  8. @Bean
  9. public CustomHandlerMapping customHandlerMapping() {
  10. return new CustomHandlerMapping(applicationContext);
  11. }
  12. }
创建 CustomHandlerMappingController 
  1. @Component("chmc")
  2. public class CustomHandlerMappingController {
  3. @ResponseBody
  4. public String hello() {
  5. return "hello HandlerMapping! ";
  6. }
  7. }
发送请求 localhost:8080?beanName=chmc&method=hello

自定义 handlerMapping 生效

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/718809
推荐阅读
相关标签
  

闽ICP备14008679号