
每一位 Spring Boot 开发者都享受过这种"魔法":你只需要在 pom.xml 中添加一个 spring-boot-starter-web 依赖,你的应用就"凭空"获得了启动 Tomcat、配置 Spring MVC 的能力。
你有没有想过,Spring Boot 是如何"发现"并"激活"这些藏在 starter 里的配置的?在 Spring Boot 2.7 之前,这个"魔法"的秘密,就藏在一个看似普通、但却无比核心的文件里------spring.factories。
本文将带你深入这个文件,理解它为什么存在(它的天才设计),以及它在 Spring Boot 3.0 之后**为什么又被"废弃"**了。
1. 为什么需要 spring.factories?------ Java SPI 的局限性
在 spring.factories 出现之前,Java 有一套自己的"服务发现"机制,叫做 SPI (Service Provider Interface)。
- Java SPI 机制: 遵循一个约定:在 JAR 包的
META-INF/services/目录下,放置一个以"接口全限定名"命名的文件,文件内容是"实现类的全限定名"。 - 示例:
mysql-connector-java.jar中有- 文件:
META-INF/services/java.sql.Driver - 内容:
com.mysql.cj.jdbc.Driver
这样,DriverManager就能通过ServiceLoader.load(Driver.class)找到所有实现了Driver接口的驱动。
- 文件:
Java SPI 的局限性:
- 一个文件一个接口:
java.sql.Driver文件只能存Driver的实现,MyService接口需要另一个com.example.MyService文件。 - 加载时机:
ServiceLoader通常是按需(懒加载)的。
Spring Boot 的野心:
Spring Boot 在启动时,需要一次性 加载很多种不同的组件,例如:
- 所有需要自动配置的类 (
EnableAutoConfiguration) - 所有应用上下文初始化器 (
ApplicationContextInitializer) - 所有应用启动监听器 (
ApplicationListener) - 所有启动失败分析器 (
FailureAnalyzer) - ...
如果用 Java SPI,Spring Boot 启动时就不得不在 Classpath 下扫描几十种不同的 META-INF/services/ 文件,这既混乱又低效。
spring.factories 的诞生:
Spring 团队借鉴了 SPI 的思想,但创造了一个更强大的"变体"。
- 设计思想: "我们不要几十本零散的电话簿,我们要做一本包罗万象的、分类清晰的'城市黄页'。"
- 这就是
spring.factories: 它始终位于META-INF/spring.factories这一个 固定的位置。文件内部使用.properties格式,通过不同的 Key 来分类列出所有需要被加载的组件。
2. spring.factories 的工作原理:一本"黄页"
spring.factories 是一个简单的 .properties 文件,其内容结构如下(以 spring-boot-autoconfigure 包为例):
properties
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.LoggingApplicationListener
# Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer
# Auto Configurations
# 这是最核心的键
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
... (还有上百个)
工作流程图(Spring Boot 2.x):
SpringApplication.run() 启动 调用 SpringFactoriesLoader.loadFactoryNames() 扫描所有 JAR 包的 Classpath 查找所有 'META-INF/spring.factories' 文件 解析所有 .factories 文件
(这是一个 IO 密集型操作) 根据 Key (如 EnableAutoConfiguration)
将所有实现类的全限定名合并到一个 List Spring Boot 容器拿到这个 List 对 List 中的自动配置类
进行 @Conditional 条件判断 只实例化 'matches()' 返回 true 的 Bean 结束: 自动配置完成
图解:
- 加载: Spring Boot 启动时,
SpringFactoriesLoader会遍历 Classpath 上的所有 JAR 包,找到所有 的spring.factories文件。 - 合并: 将这些文件中的内容全部加载到内存,并根据相同的 Key(例如
...EnableAutoConfiguration)将所有的类名合并到一个List<String>中。 - 使用: 自动配置模块拿到这个包含上百个配置类名称的
List,然后依次进行@Conditional判断,最终只实例化那些满足条件的 Bean。
3. 为什么要"废弃" spring.factories?------ "成也萧何,败也萧何"
spring.factories 这个设计非常成功,以至于它成为了它自己成功的"受害者"。随着 Spring 生态(尤其是 Spring Cloud)的爆炸式增长,这个"黄页"变得不堪重负。
核心痛点:
-
性能瓶颈(IO密集):
spring.factories是一个字符串列表 。Spring Boot 在每次启动时,都必须:- 遍历 Classpath 上的每一个 JAR。
- 打开每一个
spring.factories文件(可能一个项目中有几十个)。 - 解析这个(可能很庞大的)
.properties文件。 - 将所有字符串加载到内存中,再去重、过滤。
- 在大型 Spring Cloud 项目中,这个过程会累积成一个不可忽视的启动时开销。
-
"上帝文件" (God File):
这个文件什么都管。
Listener,Initializer,AutoConfiguration... 所有的扩展点都堆在同一个文件里,职责混乱,可读性差。 -
AOT/GraalVM 不友好:
这种基于"扫描 Classpath"和"字符串反射"的机制,对于 AOT (Ahead-of-Time) 编译 和 GraalVM 原生镜像技术非常不友好。AOT 编译器希望在编译时就明确知道哪些 Bean 需要被创建,而不是在运行时才去动态扫描和发现。
4. "新王当立":Spring Boot 3.0+ 的新约定
从 Spring Boot 2.7 开始(并在 3.0 中成为正式标准),Spring 团队引入了一种新的、更高效、更专一的自动配置加载机制,彻底取代了 spring.factories。
新约定:
不再使用一个统一的 spring.factories 文件,而是为每一种 SPI 扩展点,提供一个专属的 、独立的文件。
文件路径: META-INF/spring/ (注意,不再是 services/)
| 扩展点类型 | Spring Boot 2.x (旧) | Spring Boot 3.x (新) |
|---|---|---|
| 自动配置 | META-INF/spring.factories ...EnableAutoConfiguration |
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
| 启动监听器 | META-INF/spring.factories ...ApplicationListener |
META-INF/spring/org.springframework.context.ApplicationListener.imports |
| 启动初始化器 | META-INF/spring.factories ...ApplicationContextInitializer |
META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports |
| ... | ... | ... |
新机制的优势:
- 职责单一:
AutoConfiguration.imports文件中只包含 自动配置类。ApplicationListener.imports文件中只包含监听器。结构极其清晰。 - 性能提升 (按需加载): Spring Boot 启动时,如果它只需要 加载自动配置,它会直奔
AutoConfiguration.imports这一个文件去读取,而完全无视 其他如ApplicationListener.imports等文件。 - AOT 友好: 这种确定性的、专一的文件格式,使得 AOT 编译器在构建时可以非常容易地静态分析和处理。
示例:spring.factories vs. AutoConfiguration.imports
-
旧(
spring.factories):propertiesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.MyAutoConfig1,\ com.example.MyAutoConfig2 -
新(
AutoConfiguration.imports):com.example.MyAutoConfig1 com.example.MyAutoConfig2(文件内容就是类名的简单列表,解析更快)
5. 总结
spring.factories 是 Spring Boot 早期实现"约定优于配置"和自动装配的天才设计。它通过一份"黄页",巧妙地解决了 Java SPI 的局限性,支撑起了 Spring Boot 庞大的生态系统。
然而,随着项目规模的增长和云原生(AOT)时代的到来,这个"什么都管"的"上帝文件"逐渐成为了性能瓶颈。Spring Boot 3.0 将其"废弃",进化为更专一、更轻量、更高性能 的 META-INF/spring/*.imports 文件列表,完成了它光荣的历史使命。