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

相关推荐
MonkeyKing_sunyuhua23 分钟前
Ehcache、Caffeine、Spring Cache、Redis、J2Cache、Memcached 和 Guava Cache 的主要区别
redis·spring·memcached
心平愈三千疾1 小时前
通俗理解JVM细节-面试篇
java·jvm·数据库·面试
漂流瓶jz1 小时前
清除浮动/避开margin折叠:前端CSS中BFC的特点与限制
前端·css·面试
考虑考虑1 小时前
@FilterRegistration和@ServletRegistration注解
spring boot·后端·spring
我不吃饼干9 天前
鸽了六年的某大厂面试题:你会手写一个模板引擎吗?
前端·javascript·面试
我不吃饼干9 天前
鸽了六年的某大厂面试题:手写 Vue 模板编译(解析篇)
前端·javascript·面试
LyaJpunov9 天前
深入理解 C++ volatile 与 atomic:五大用法解析 + 六大高频考点
c++·面试·volatile·atomic
前端fighter9 天前
为什么需要dependencies 与 devDependencies
前端·javascript·面试
前端fighter9 天前
Vuex 与 Pinia:全面解析现代 Vue 状态管理的进化之路
前端·vue.js·面试
亲爱的非洲野猪9 天前
一次性理解Java垃圾回收--简单直接方便面试时使用
java·jvm·面试