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 work
    return 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;
    }

    @Override
    public 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);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

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

创建 HandlerMappingConfig
@Configuration
public class HandlerMappingConfig implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Bean
    public CustomHandlerMapping customHandlerMapping() {
        return new CustomHandlerMapping(applicationContext);
    }
}
创建 CustomHandlerMappingController 
@Component("chmc")
public class CustomHandlerMappingController {

    @ResponseBody
    public String hello() {
        return "hello HandlerMapping! ";
    }
}
发送请求 localhost:8080?beanName=chmc&method=hello

自定义 handlerMapping 生效

  • 32
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文档内容 一、 Spring介绍 1 1.1、SpringBoot简介 1 1.2、系统要求: 1 1.3、SpringBootSpringMVC区别 1 1.4、SpringBootSpringCloud区别 2 1.5常见错误 2 二、快速入门 2 2.1、创建一个Maven工程 2 2.2、pom文件引入依赖 3 2.3、编写HelloWorld服务 3 2.4、@RestController 4 2.5、@EnableAutoConfiguration 4 2.6 SpringApplication.run(HelloController.class, args); 4 2.7、SpringBoot启动方式1 4 2.8、SpringBoot启动方式2 4 2.9、SpringBoot启动方式3 5 三、 Web开发 5 3.1、静态资源访问 5 3.2、渲染Web页面 5 3.3、使用Freemarker模板引擎渲染web视图 6 3.3.1、pom文件引入: 6 3.3.2、后台代码 6 3.3.3、前台代码 6 3.3.4、Freemarker其他用法 7 3.3.5、Freemarker配置 8 3.4、使用JSP渲染Web视图 8 3.4.1、pom文件引入以下依赖 8 3.4.2、在application.properties创建以下配置 9 3.4.3、后台代码 9 3.5、全局捕获异常 10 四、 数据访问 10 4.1、springboot整合使用JdbcTemplate 10 4.2、springboot整合使用mybatis 12 4.3、springboot整合使用springjpa 18 4.4、springboot整合多数据源 19 五、 事物管理 25 5.1.1SpringBoot整合事物管理 25 5.1.2SpringBoot分布式事物管理 25 六、 日志管理 30 6.1使用log4j记录日志 30 6.2使用AOP统一处理Web请求日志 32 6.3Spring Boot集成lombok让代码更简洁 33 七、 缓存支持 35 7.1注解配置与EhCache使用 35 7.2使用Redis集成缓存 37 八、 热部署 37 8.1 什么是热部署 37 8.2 项目演示案例 37 8.3 热部署原理 37 8.4 Devtools依赖 38 8.5 Devtools原理 38 九、 监控管理 38 Actuator监控应用 38 Maven依赖 38 YML配置 39 Actuator访问路径 40 Admin-UI分布式微服务监控中心 40 Admin-UI-Server 40 Admin-UI-Client 41 十、 性能优化 43 组件自动扫描带来的问题 43 将Servlet容器变成Undertow 44 SpringBoot JVM参数调优 44 十一、 2.0版本新特性 45 以Java 8 为基准 45 内嵌容器包结构调整 45 Servlet-specific 的server properties调整 45 Actuator 默认映射 46 Spring Loaded不再支持 46 支持Quartz Scheduler 46 OAuth 2.0 支持 46 支持Spring WebFlux 46 版本要求 46 十二、 其他内容 47 12.1、使用@Scheduled创建定时任务 47 12.2、使用@Async实现异步调用 47 12.3、自定义参数 49 12.4、多环境配置 50 12.5、修改端口号 50 12.6、SpringBoot yml 使用 50 12.7、SpringBoot整合拦截器 51 12.8、发布打包 52

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值