深入 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 行——让你在同时打开多个构建文件时,能立刻辨认出当前所在的模块:
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-jdbc、spring-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-webmvc 和 spring-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.asm、org.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:
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
代码库导航:实用技巧
了解了构建结构之后,以下是一些阅读源码的实用建议:
-
从接口入手,而非实现。 Spring 的接口附有详尽的 Javadoc。阅读
BeanFactory.java能让你对设计意图的理解远超任何实现类。 -
顺着
extends链追踪。 Spring 的实现继承层次深,但每一层都有其意义。理解一个类存在的原因,比死记它做了什么更重要。 -
善用模块边界。 排查 Web 问题时,目标通常在
spring-web、spring-webmvc或spring-webflux之中;排查依赖注入问题时,则在spring-beans。模块结构就是最好的调试向导。 -
先看
.gradle文件。 每个模块的${module-name}.gradle文件列出了它的依赖,由此可以快速判断当前所处的抽象层级以及可用的外部 API。
提示: 阅读 Spring 源码时,Gradle 文件中的
optional()依赖声明表示某个功能仅在用户提供了对应库时才可用。这与 Spring Boot 的自动配置机制直接对应:只要 classpath 上存在该库,相应功能便会自动激活。
下一步
有了这张模块地图,我们已经准备好深入其中最核心的模块:spring-beans。在下一篇文章中,我们将从 getBean() 这个最简单的方法出发,沿着 BeanFactory 接口继承体系追踪七个子接口,一直到拥有 2800 行代码的 DefaultListableBeanFactory 实现类,并完整梳理 Bean 的创建流程——从原始 Class 对象到完成依赖注入的单例实例。