你有没有阅读过Spring的源码

前言

如果只知道SpringBoot的核心注解是@SpringBootApplication,未免显得只知道八股文,而缺乏对SpringBoot源码的认识。

此处以一个数据中台项目中查询接口动态注册、注销使用的RequestMappingHandlerMapping类,展开进行分析。

分析

在数据中台项目中,动态注册、注销请求用到了SpringMVC提供的RequestMappingHandlerMapping类,这个类的源码我阅读过。

核心流程图:

css 复制代码
graph TD
    A[容器启动] --> B[afterPropertiesSet]
    B --> C[扫描所有@Controller Bean]
    C --> D[解析类和方法上的@RequestMapping]
    D --> E[生成RequestMappingInfo]
    E --> F[注册到MappingRegistry]
    F --> G[等待请求]
    G --> H[接收HTTP请求]
    H --> I[解析请求路径和参数]
    I --> J[查找匹配的RequestMappingInfo]
    J --> K[选择最佳匹配]
    K --> L[返回HandlerMethod]

Spring MVC框架中,RequestMappingHandlerMapping 是处理 @RequestMapping 注解的核心类,负责将HTTP请求映射到对应的控制器方法。以下是对其源码的详细分析,从设计结构、核心方法到关键流程逐步展开:

  1. 类继承与职责:

RequestMappingHandlerMapping继承自AbstractHandlerMethodMapping<RequestMappingInfo>,而后者实现了 HandlerMapping 接口,主要职责包括:

  • 扫描并注册 :在应用启动时扫描所有控制器Bean,解析类和方法上的 @RequestMapping 注解,生成映射关系。
  • 请求匹配 :根据HTTP请求的URL、方法、头等信息,找到最匹配的处理器方法(HandlerMethod)。
  • 路径处理 :支持Ant风格路径、路径变量、内容协商等特性。

关键父类方法:

  • registerHandlerMethod():将 HandlerMethod 与其对应的 RequestMappingInfo 注册到内部映射表。
  • getMappingForMethod():在子类中实现,用于生成方法级别的 RequestMappingInfo
  1. 初始化与Bean扫描

入口方法:afterPropertiesSet(),在Spring容器初始化时,该方法被调用,触发控制器方法的扫描与注册。

typescript 复制代码
@Override
public void afterPropertiesSet() {
    // 配置跨域相关(CORS)
    super.afterPropertiesSet();
    // 初始化路径匹配工具(如PathPatternParser或AntPathMatcher)
    if (this.mappingRegistry == null) {
        this.mappingRegistry = new MappingRegistry();
    }
}

关键流程:

  • 扫描所有Bean :遍历容器中的Bean,检查是否带有@Controller@RequestMapping注解。
  • 解析控制器方法 :对每个符合条件的Bean,解析其所有方法,生成RequestMappingInfo
  • 注册映射关系 :将RequestMappingInfoHandlerMethod存入MappingRegistry
  1. 生成RequestMappingInfo

核心方法:getMappingForMethod(),该方法解析方法和类上的@RequestMapping注解,合并生成最终的RequestMappingInfo

ini 复制代码
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 1. 解析方法上的@RequestMapping
    RequestMappingInfo methodInfo = createRequestMappingInfo(method);
    if (methodInfo == null) {
        return null;
    }

    // 2. 解析类上的@RequestMapping(作为前缀)
    RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
    if (typeInfo != null) {
        // 合并类和方法级别的配置(路径、请求方法等)
        methodInfo = typeInfo.combine(methodInfo);
    }

    return methodInfo;
}

注解合并规则:

  • 路径拼接 :类路径 + 方法路径(如/api + /users/api/users)。
  • 请求方法:若类和方法均指定,以方法级别为准。
  • 其他属性 (如params, headers):合并为逻辑"与"条件。
  1. 注册映射到MappingRegistry

核心类:MappingRegistry(内部类),存储所有注册的映射关系,支持快速查找。

arduino 复制代码
class MappingRegistry {
    // 映射表:Key=RequestMappingInfo, Value=MappingRegistration
    private final Map<RequestMappingInfo, MappingRegistration> registry = new HashMap<>();

    // URL路径直接映射表(优化快速匹配)
    private final MultiValueMap<String, RequestMappingInfo> pathLookup = new LinkedMultiValueMap<>();

    public void register(RequestMappingInfo info, HandlerMethod handlerMethod) {
        // 1. 去重检查
        if (this.registry.containsKey(info)) {
            throw new IllegalStateException("重复的映射: " + info);
        }
        // 2. 注册到映射表
        this.registry.put(info, new MappingRegistration(info, handlerMethod));
        // 3. 按URL路径分类存储(用于快速匹配)
        for (String path : info.getDirectPaths()) {
            this.pathLookup.add(path, info);
        }
    }
}
  1. 请求匹配流程

入口方法:getHandlerInternal(),当请求到达时,DispatcherServlet 调用此方法查找匹配的 HandlerMethod

java 复制代码
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 1. 获取请求路径(解析并解码)
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 2. 查找所有匹配的RequestMappingInfo
    List<RequestMappingInfo> matches = new ArrayList<>();
    addMatchingMappings(this.mappingRegistry.getMappingsByUrl(lookupPath), request, matches);
    // 3. 排序并选择最佳匹配
    if (!matches.isEmpty()) {
        RequestMappingInfo bestMatch = selectBestMatch(matches, request);
        // 4. 返回对应的HandlerMethod
        return this.mappingRegistry.getHandlerMethod(bestMatch);
    }
    return null;
}

匹配规则:

  • 路径匹配:优先匹配直接路径,支持通配符和路径变量。
  • 条件匹配:检查请求方法、参数、头部等。
  • 排序规则:更具体的路径(如无通配符)优先级更高。
  1. 路径匹配策略
  • PathPatternParser (默认):Spring 5.3+引入,基于预解析的路径模式,性能更优。
  • AntPathMatcher :传统Ant风格路径匹配,可通过配置切换。
typescript 复制代码
// 配置PathPatternParser(Spring Boot配置示例)
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setPatternParser(new PathPatternParser());
    }
}
  1. 跨域处理(CORS)

通过 @CrossOrigin 注解或全局配置,RequestMappingHandlerMapping 会生成对应的 CorsConfiguration,并在匹配时应用。

scss 复制代码
// 在注册映射时处理CORS配置
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
    // 解析@CrossOrigin注解
    CorsConfiguration corsConfig = getCorsConfiguration(handler, method, mapping);
    super.registerHandlerMethod(handler, method, mapping, corsConfig);
}
  1. 异步请求支持

对于返回 DeferredResultCallable 等方法,RequestMappingHandlerMapping 会生成对应的 HandlerMethod,后续由 RequestMappingHandlerAdapter 处理异步执行。

相关推荐
weixin_437398216 小时前
RabbitMQ深入学习
java·分布式·后端·spring·spring cloud·微服务·rabbitmq
bing_1587 小时前
Spring MVC 视图解析器 (ViewResolver) 如何配置? Spring Boot 是如何自动配置常见视图解析器的?
spring boot·spring·mvc
大学生小郑8 小时前
Go语言八股之channel详解
面试·golang
残花月伴11 小时前
springCloud/Alibaba常用中间件之Nacos服务注册与发现
spring·spring cloud·中间件
Kx…………11 小时前
Java EE(Spring+Spring MVC+MyBatis)从入门到精通企业级应用开发教程——1初识MyBatis框架
学习·spring·java-ee·mvc·mybatis
超级无敌永恒暴龙战士11 小时前
SpringMVC-执行流程
spring·servlet·springmvc
黄俊懿13 小时前
【深入理解SpringCloud微服务】手写实现一个微服务分布式事务组件
java·分布式·后端·spring·spring cloud·微服务·架构师
枫夜求索阁14 小时前
大模型文件类型揭秘:从基础到面试挑战
人工智能·面试·职场和发展·大模型
有梦想的攻城狮14 小时前
spring中的@Inject注解详情
java·后端·spring·inject
Top`14 小时前
服务预热原理
java·后端·spring