Read OSS

深入 Spring Framework Monorepo:架构、模块与构建系统

中级

前置知识

  • 具备 Java 基础知识及 Gradle 构建系统使用经验
  • 有 Spring Framework 用户侧使用经验

深入 Spring Framework Monorepo:架构、模块与构建系统

Spring Framework 自 2002 年起持续演进,是现存历史最悠久的开源 Java 项目之一。时至今日,7.x 版本已涵盖 22 个模块,整个项目以 Gradle monorepo 的形式组织,使用 JDK 25 工具链构建,同时以 Java 17 字节码为目标。要理解 Spring 的 IoC 容器、AOP 或 Web 层的工作原理,首先需要对代码库本身的组织方式有清晰的认识。本文就是这张导览地图。

仓库全貌

仓库根目录看似简单,实则设计精巧。settings.gradle 声明了所有模块,build.gradle 配置共享约定,而每个模块都有一个以 ${project.name}.gradle 命名的构建文件,而非标准的 build.gradle。这个命名技巧——对应 settings.gradle 第 34–36 行——让你在同时打开多个构建文件时,能立刻辨认出当前所在的模块:

settings.gradle#L33-L36

rootProject.name = "spring"
rootProject.children.each { project ->
    project.buildFileName = "${project.name}.gradle"
}
目录 用途
spring-core 基础工具类、类型系统、资源加载,以及内嵌的 ASM/CGLIB/JavaPoet
spring-beans IoC 容器:BeanFactory、BeanDefinition、依赖注入
spring-aop AOP alliance 模型、代理基础设施
spring-expression SpEL——Spring 表达式语言
spring-context ApplicationContext、注解配置、事件系统、任务调度
spring-tx 事务抽象、PlatformTransactionManager
spring-jdbcspring-r2dbc JDBC/R2DBC 数据访问模板
spring-orm JPA/Hibernate 集成
spring-web MVC 与 WebFlux 共享的 HTTP 抽象层
spring-webmvc 基于 Servlet 的 MVC(DispatcherServlet)
spring-webflux 响应式 Web(DispatcherHandler,基于 Reactor)
spring-websocket WebSocket 支持
spring-messaging 消息抽象(STOMP 等)
spring-jms JMS 集成
spring-test 测试工具(MockMvc、TestContext)
spring-aspects AspectJ 集成
spring-instrument 用于类插桩的 JVM agent
spring-context-indexer 构建时注解索引器
spring-context-support 缓存管理器、邮件、FreeMarker
spring-core-test spring-core 的测试夹具
spring-oxm 对象/XML 编组

提示: 根目录的 build.gradle 在第 13–14 行将子项目分为两个集合:moduleProjects(名称以 spring- 开头的模块)和 javaProjects(排除 framework-* 工具项目后的所有项目)。这两个集合在整个构建脚本中被广泛用于按需应用插件。

22 模块的分层依赖图

Spring 的各模块形成严格的分层架构,依赖关系单向流动——从高层模块指向底层基础模块,不允许循环依赖(由 ArchUnit 在构建时强制检查,稍后会详细介绍)。

flowchart TD
    subgraph "Layer 1: Foundation"
        core["spring-core"]
    end

    subgraph "Layer 2: IoC"
        beans["spring-beans"]
    end

    subgraph "Layer 3: AOP & Expression"
        aop["spring-aop"]
        expression["spring-expression"]
    end

    subgraph "Layer 4: Application Context"
        context["spring-context"]
        ctxSupport["spring-context-support"]
    end

    subgraph "Layer 5: Data Access"
        tx["spring-tx"]
        jdbc["spring-jdbc"]
        orm["spring-orm"]
        r2dbc["spring-r2dbc"]
    end

    subgraph "Layer 6: Web Foundation"
        web["spring-web"]
    end

    subgraph "Layer 7: Web Frameworks"
        webmvc["spring-webmvc"]
        webflux["spring-webflux"]
        websocket["spring-websocket"]
    end

    beans --> core
    aop --> beans
    aop --> core
    expression --> core
    context --> aop
    context --> beans
    context --> expression
    context --> core
    ctxSupport --> context
    tx --> beans
    tx --> core
    jdbc --> tx
    jdbc --> beans
    orm --> jdbc
    orm --> tx
    r2dbc --> tx
    r2dbc --> core
    web --> beans
    web --> core
    webmvc --> web
    webmvc --> aop
    webmvc --> context
    webmvc --> expression
    webflux --> web
    webflux --> beans
    webflux --> core
    websocket --> web
    websocket --> context

这里有一个关键设计值得关注:spring-web 是共享的 HTTP 抽象层。spring-webmvcspring-webflux 都依赖它,但两者之间没有任何依赖关系。MVC 模块依赖 Servlet API,WebFlux 模块依赖 Reactor。正是这种清晰的边界划分,让 Spring 得以同时支持两种编程模型,互不干扰。

自定义 Gradle 构建系统:buildSrc 插件

buildSrc 目录包含一套自定义 Gradle 插件体系,用于在所有 22 个模块中统一执行构建约定。入口点是 ConventionsPlugin

buildSrc/src/main/java/org/springframework/build/ConventionsPlugin.java#L38-L50

该插件通过根目录的 build.gradle 应用于每个 Java 项目:

apply plugin: 'org.springframework.build.conventions'

它统筹协调五个约定类,每个类负责一项具体关切:

flowchart LR
    CP[ConventionsPlugin] --> AP[ArchitecturePlugin]
    CP --> CC[CheckstyleConventions]
    CP --> JC[JavaConventions]
    CP --> KC[KotlinConventions]
    CP --> TC[TestConventions]

编译策略由 JavaConventions 负责。它配置 JDK 25 工具链,同时通过 --release 标志将目标字节码版本设为 Java 17——这意味着 Spring 可以在 multi-release JAR 的特定版本切片中使用 JDK 25 的编译器改进和 API,同时将 Java 17 作为最低运行时要求:

buildSrc/src/main/java/org/springframework/build/JavaConventions.java#L48-L54

注意第 67 行编译器参数中的 -Werror 标志——生产代码中所有警告都会被视为错误,但测试代码适当放宽了这一限制。这是一个有意为之的选择:在早期捕获问题,同时不给测试代码增加额外负担。

通过 ArchUnit 强制执行架构约束

Spring 不只是期望各模块保持整洁,而是在构建时通过 ArchUnit 强制执行架构规则。ArchitectureRules 类定义了所有模块必须满足的结构不变式:

buildSrc/src/main/java/org/springframework/build/architecture/ArchitectureRules.java#L28-L100

规则分为三类:

flowchart TD
    AR[ArchitectureRules] --> PT[Package Tangle Detection]
    AR --> FA[Forbidden API Calls]
    AR --> FT[Forbidden Type Dependencies]

    PT -->|"SlicesRuleDefinition"| NoCycles["No package cycles allowed"]
    FA --> NoLower["No String.toLowerCase without Locale"]
    FA --> NoUpper["No String.toUpperCase without Locale"]
    FT --> NoSLF4J["No SLF4J LoggerFactory"]
    FT --> NoSpringNullable["No org.springframework.lang.Nullable"]
    FT --> NoSpringNonNull["No org.springframework.lang.NonNull"]

被禁止的类型列表记录了一段迁移历程。Spring 正在将自身 org.springframework.lang 包下的 @Nullable/@NonNull 注解迁移到 JSpecify 注解。第 56–58 行的 ArchUnit 规则禁止任何人导入旧注解,以此强制推进迁移进程。同样,org.slf4j.LoggerFactory 也在禁止之列——Spring 使用 Apache Commons Logging 作为日志门面,这是一个刻意的选择,避免强迫用户使用特定的日志框架。

SpringSlices 内部类(第 76–99 行)值得特别关注:它明确将内嵌的第三方包(org.springframework.asmorg.springframework.cglib 等)排除在循环检测之外,因为这些是 Spring 无法掌控的重定位第三方代码。

提示: 如果你也在构建大型 monorepo,Spring 将 ArchUnit 规则直接内嵌到构建插件中(而非以测试类的形式存在)的做法,能确保规则在所有模块中保持一致,无需反复拷贝。

Shadow/Repack 与 Multi-Release JAR 策略

spring-core 最特殊的构建配置之一,是将四个第三方库直接打包进自己的 JAR,并以 Spring 的命名空间重新定位。这样做的目的是避免应用程序使用不同版本的同一库时产生 classpath 冲突。

spring-core/spring-core.gradle#L30-L35

repack 策略使用 Shadow Gradle 插件对包命名空间进行重定位:

原始包名 重定位后包名
com.palantir.javapoet org.springframework.javapoet JavaPoet(代码生成)
org.objenesis org.springframework.objenesis Objenesis(对象实例化)
ASM(以源码形式内嵌) org.springframework.asm ASM(字节码操作)
CGLIB(以源码形式内嵌) org.springframework.cglib CGLIB(子类生成)
flowchart LR
    subgraph "Third-Party JARs"
        JP["javapoet-0.10.0.jar"]
        OB["objenesis-3.5.jar"]
    end

    subgraph "ShadowJar Tasks"
        JPRJ["javapoetRepackJar"]
        OBRJ["objenesisRepackJar"]
    end

    subgraph "spring-core.jar"
        SJP["org.springframework.javapoet.*"]
        SOB["org.springframework.objenesis.*"]
        SASM["org.springframework.asm.*"]
        SCGLIB["org.springframework.cglib.*"]
    end

    JP --> JPRJ --> SJP
    OB --> OBRJ --> SOB

ASM 和 CGLIB 并非通过 Shadow 进行 repack,而是以重定位后的源码形式直接维护在 spring-core 的源码树中。这让 Spring 团队能够完全掌控,按需应用补丁和优化。

MultiReleaseJarPlugin 支持针对特定 Java 版本进行优化。spring-core 模块声明了对 Java 21 和 24 的 multi-release 支持:

spring-core/spring-core.gradle#L13-L15

multiRelease {
    releaseVersions 21, 24
}

这意味着 spring-core 发布的 JAR 包含一套标准的 Java 17 代码库,同时在 META-INF/versions/21/META-INF/versions/24/ 下存放了针对对应版本优化的类文件。当运行在 JDK 24 上时,JVM 会自动加载这些优化后的类版本。

Framework Platform BOM 与依赖管理

22 个模块涉及数十个第三方依赖,版本管理稍有不慎便会陷入混乱。Spring 通过在 framework-platform.gradle 中定义统一的 Bill of Materials(BOM)来解决这一问题:

framework-platform/framework-platform.gradle#L1-L25

该文件使用 Gradle 的 java-platform 插件锁定所有第三方依赖的版本——从 Jackson 2.21.2、JUnit 6.0.3 到 Reactor BOM 2025.0.4。所有模块随后通过根构建中的 enforcedPlatform() 消费这个 platform:

build.gradle#L39-L51

flowchart TD
    BOM["framework-platform.gradle<br/>(java-platform)"]
    
    ROOT["build.gradle<br/>enforcedPlatform()"]
    
    MOD1["spring-core"]
    MOD2["spring-beans"]
    MOD3["spring-web"]
    MODN["... all other modules"]
    
    BOM --> ROOT
    ROOT --> MOD1
    ROOT --> MOD2
    ROOT --> MOD3
    ROOT --> MODN

enforcedPlatform() 的作用至关重要——与普通的 platform() 不同,它会覆盖所有传递依赖的版本。即使某个库通过传递依赖引入了 Jackson 2.18,enforcedPlatform() 也会强制将其升级到 2.21.2,从而保证整个框架的版本一致性。

spring-module.gradle 共享脚本(通过根构建第 88–90 行应用于所有 spring-* 模块)添加了 java-library 插件、JMH 基准测试支持、用于 JSpecify 校验的 io.spring.nullability 插件,以及标准的发布配置:

gradle/spring-module.gradle#L1-L9

代码库导航:实用技巧

了解了构建结构之后,以下是一些阅读源码的实用建议:

  1. 从接口入手,而非实现。 Spring 的接口附有详尽的 Javadoc。阅读 BeanFactory.java 能让你对设计意图的理解远超任何实现类。

  2. 顺着 extends 链追踪。 Spring 的实现继承层次深,但每一层都有其意义。理解一个类存在的原因,比死记它做了什么更重要。

  3. 善用模块边界。 排查 Web 问题时,目标通常在 spring-webspring-webmvcspring-webflux 之中;排查依赖注入问题时,则在 spring-beans。模块结构就是最好的调试向导。

  4. 先看 .gradle 文件。 每个模块的 ${module-name}.gradle 文件列出了它的依赖,由此可以快速判断当前所处的抽象层级以及可用的外部 API。

提示: 阅读 Spring 源码时,Gradle 文件中的 optional() 依赖声明表示某个功能仅在用户提供了对应库时才可用。这与 Spring Boot 的自动配置机制直接对应:只要 classpath 上存在该库,相应功能便会自动激活。

下一步

有了这张模块地图,我们已经准备好深入其中最核心的模块:spring-beans。在下一篇文章中,我们将从 getBean() 这个最简单的方法出发,沿着 BeanFactory 接口继承体系追踪七个子接口,一直到拥有 2800 行代码的 DefaultListableBeanFactory 实现类,并完整梳理 Bean 的创建流程——从原始 Class 对象到完成依赖注入的单例实例。