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

相关推荐
uhakadotcom12 分钟前
Google Cloud IoT 平台入门:基础架构与示例详解
后端·面试·github
雷渊13 分钟前
ES不支持decimal,如何避免丢失精度?
java·后端·面试
雷渊14 分钟前
ES支持乐观锁吗?如何实现的?
java·后端·面试
独立开阀者_FwtCoder17 分钟前
【完整汇总】近 5 年 JavaScript 新特性完整总览
前端·javascript·面试
雷渊21 分钟前
ES支持哪些数据类型,和mysql之间的映射关系是怎么样的?
java·后端·面试
uhakadotcom34 分钟前
程序化广告十年总结:通俗易懂的基础知识与实战案例解析
后端·面试·github
小杨xyyyyyyy1 小时前
计算机网络 - TCP协议
网络·网络协议·tcp/ip·计算机网络·面试
尘寰ya1 小时前
如何实现一个“纯净”的空对象(无原型链属性)?
javascript·面试·原型模式
啊吧啊吧曾小白2 小时前
聊一聊前端日常使用的try...catch...finally
前端·javascript·面试
zayyo2 小时前
前端性能优化:图片懒加载全攻略
前端·面试·性能优化