你有没有阅读过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 处理异步执行。

相关推荐
banpu5 分钟前
Spring相关
数据库·spring·sqlserver
Swift社区26 分钟前
死锁:线程卡死不是偶然,而是设计问题
java·spring·maven
风止何安啊29 分钟前
Set/Map+Weak三剑客的骚操作:JS 界的 “去重王者” ,“万能钥匙”和“隐形清洁工”
前端·javascript·面试
凛_Lin~~33 分钟前
安卓 面试八股文整理(原理与性能篇)
android·java·面试·安卓
程序员爱钓鱼2 小时前
用Python开发“跳一跳”小游戏——从零到可玩
后端·python·面试
程序员爱钓鱼2 小时前
Python 源码打包成.whl文件的完整指南
后端·python·面试
努力学算法的蒟蒻2 小时前
day47(12.28)——leetcode面试经典150
算法·leetcode·面试
LJianK13 小时前
前后端接口常见传参
java·spring
YDS8293 小时前
SpringCloud —— 分布式事务管理Seata详解
分布式·spring·spring cloud·seata
东东的脑洞3 小时前
【面试突击】TCP 四次挥手详解
网络·tcp/ip·面试