SpringBoot 挥别 spring.factories:老伙计退场,新方案真香!
SpringBoot 3.0 发布时,干了一件挺“大事”——把陪伴咱们多年的 spring.factories 给“请”下了台。这波操作引发了不少开发者的讨论,有人觉得迁移费点劲,有人用了新方案后直呼“舒服”。今天咱们就来好好聊聊,这个老伙计为啥会被替代,新方案又好在哪儿,以及怎么顺顺利利完成迁移。
一、先聊聊老熟人:spring.factories 是啥?
在说它“退休”的事儿之前,咱们得先搞明白,这个 spring.factories 到底在 SpringBoot 生态里扮演啥角色。其实它不是 SpringBoot 凭空造的,而是 Java SPI 机制的“变种选手”,说白了就是 SpringBoot 自动配置的“幕后推手”,没它,很多“开箱即用”的功能都玩不转。
👉 欢迎加入小哈的星球,你将获得: 专属的项目实战(多个项目) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
-
新项目:《Spring AI 项目实战》正在更新中..., 基于 Spring AI + Spring Boot 3.x + JDK 21;
-
《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍;演示地址:http://116.62.199.48:7070/
-
《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;
-
专栏阅读地址:https://www.quanxiaoha.com/column
截止目前,累计输出 100w+ 字,讲解图 4013+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有4000+小伙伴加入

1.1 它在哪儿?能干啥?
spring.factories 特“执着”,认准了 JAR 包的 META-INF/ 目录,就固定待在那儿。它的核心作用特简单:告诉 SpringBoot“这个接口该用哪个实现类”。有了它,SpringBoot 启动时就能自动找到这些实现类,完成装配和扩展,这也是 SpringBoot “约定优于配置”的精髓所在——不用咱们手动写一堆配置,它自己就搞定了。
1.2 它的工作日常:启动时忙啥呢?
在 SpringBoot 3.0 之前,spring.factories 可是个“多面手”:自动配置类要靠它注册,ApplicationListener 这类扩展点要它声明,第三方组件想集成进来也得找它当“入口”。
它的工作全靠 SpringFactoriesLoader 这个“小助手”。应用一启动,这个类就会扫遍类路径下所有 JAR 包的 META-INF/spring.factories,把里面的键值对读出来,再把对应的类加载好。咱们先看段 SpringBoot 2.x 的核心代码感受下:
public final class SpringFactoriesLoader {
// 加载指定类型的实现类列表
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
// 核心步骤:把所有 spring.factories 都加载一遍
Map<String, List<String>> result = loadSpringFactories(classLoader);
// 后续筛选、实例化逻辑...
}
// 扫遍类路径找 spring.factories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 挨个翻 JAR 包里的 META-INF/spring.factories
// 读配置、整理成键值对...
}
// 其他辅助方法...
}
二、为啥要跟老伙计说再见?spring.factories 的那些槽点
spring.factories 退出舞台可不是 SpringBoot 团队“喜新厌旧”,实在是它在性能、兼容性这些方面,跟不上新时代的需求了。总结下来,主要是这五个“硬伤”太显眼:
槽点1:启动慢如蜗牛——全量扫描的效率坑
spring.factories 最让人头疼的就是“逢启动必扫全场”——不管用不用,都得把类路径下所有 JAR 包的这个文件翻一遍。小项目依赖少,感觉不明显;可到了微服务或者大型项目里,几十个上百个 JAR 包堆在一起,扫描起来 CPU 和 IO 都得“加班”,启动时间硬生生被拉长,这跟 SpringBoot 追求“快启动”的初心完全背道而驰。
槽点2:跟 Java 9+ 处不来——模块化的兼容难题
Java 9 搞出的模块系统(JPMS),核心就是“依赖要明说,边界要清晰”。可 spring.factories 是靠类路径“偷偷加载”的,完全不符合人家的“显式依赖”要求。比如模块想引用它里面的实现类,都没法明明白白声明,很容易把模块边界搞乱,典型的“水土不服”。
槽点3:先加载再筛选——白费力气的笨办法
spring.factories 的配置是“写死”的,不管这个类最后能不能用,先加载到内存再说,然后才靠 @Conditional 注解来“筛掉”不合适的。这就像买衣服不管合不合身先买回来,试完不合适再扔,纯属浪费资源。复杂项目里这种“无效加载”多了,性能肯定受影响。
槽点4:配置散满天——维护起来头都大
大型项目里,spring.factories 就像“散落在各个角落的小纸条”,每个依赖 JAR 包里可能都藏着一个。想找某个自动配置类是在哪儿注册的?得一个一个翻依赖的 META-INF 目录,排查起来费老劲了,还容易出现配置冲突或者重复注册的问题。
槽点5:跟 GraalVM 不兼容——原生镜像的拦路虎
支持 GraalVM 原生镜像是 SpringBoot 3.0 的重头戏,可 spring.factories 的机制跟 GraalVM 的“提前编译”模式根本合不来,这也是它被淘汰的最关键原因。具体冲突有仨,个个都是硬伤:
-
静态分析卡壳:GraalVM 做原生镜像时,得把所有代码依赖都分析清楚。可 spring.factories 是运行时才扫类路径,构建阶段根本不知道要加载啥类,直接分析失败。
-
反射配置麻烦:spring.factories 靠反射加载类,而 GraalVM 要求提前声明所有反射用到的类,这就得写一大堆额外配置,开发成本直接翻倍。
-
资源访问不匹配:原生镜像里读资源的方式跟 JVM 不一样,spring.factories 的扫描逻辑直接用不了,还得额外改代码适配。
想让 SpringBoot 跟 GraalVM 好好合作,那就只能放弃这种运行时扫路径的老办法,换成构建时就能确定的静态配置。
三、新方案登场:imports 文件凭啥“接棒”?
既然老伙计不行了,SpringBoot 3.0 就推出了 imports 文件这套新机制,把 spring.factories 的活儿全接了过来。这套新方案主打“分类管理、简单直接”,老问题基本上都解决了。
3.1 新方案的核心思路:按功能“分文件”
新机制把配置文件都统一放到 META-INF/spring/ 目录下,最贴心的是按扩展点类型拆分,一种扩展点对应一个文件,再也不用挤在一个文件里乱糟糟的了。比如:
-
自动配置类对应:AutoConfiguration.imports
-
ApplicationContextInitializer对应:ApplicationContextInitializer.imports
-
FailureAnalyzer对应:FailureAnalyzer.imports
这样一来,配置按功能分得明明白白,想找哪个扩展点的配置,直接定位对应文件就行,比以前清爽多了。
3.2 新方案的四大亮点,用了都说好
-
启动更快:需要啥扩展点就加载啥文件,不用再扫所有 JAR 包的单个文件,无效 IO 直接减少。
-
跟模块系统处得好:通过 Java 模块的“opens”声明把 META-INF/spring 目录暴露出去,完全符合 JPMS 的要求。
-
配置超简单:扔了老机制的键值对,改成“一行一个类名”,不用记复杂格式,写起来不容易错。
-
适配原生镜像无压力:静态的文件结构和类名,GraalVM 构建时就能全分析明白,完美契合。
3.3 新旧对比:一眼看出新方案多香
就拿最常用的自动配置类注册来说,新旧写法的差别特别明显,新的简直是“降维打击”:
老写法(spring.factories):又长又容易错
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfiguration
新写法(AutoConfiguration.imports):清爽到飞起
com.example.FooAutoConfiguration
com.example.BarAutoConfiguration
你看,新写法把冗余的键名和逗号都扔了,直接写类名就行,不仅直观,还不用担心漏写逗号这种低级错误。
四、实战教学:手把手教你从老的换成新的
从 SpringBoot 2.x 升级到 3.0 的小伙伴,不用慌,按扩展点类型一步步来,迁移超简单。下面是最常见场景的操作步骤:
4.1 最常用场景:自动配置类迁移
这是咱们最常碰到的情况,三步走就能搞定:
-
第一步:代码不用改,直接留着:原来的 @Configuration、@ConditionalOnXxx 这些注解都不用动,代码完全复用。比如这个配置类:
@Configuration @ConditionalOnClass(Foo.class) @EnableConfigurationProperties(FooProperties.class) public class FooAutoConfiguration { // 配置逻辑一点不变} -
第二步:新建 imports 文件:在 resources 目录下,先建 META-INF/spring/ 这个目录,然后在里面新建一个文件,名字固定叫 org.springframework.boot.autoconfigure.AutoConfiguration.imports。
-
第三步:写类名:在刚建的文件里,一行写一个自动配置类的全限定名就行,比如:
com.example.FooAutoConfiguration
4.2 其他扩展点:比如 Listener、Analyzer
像 ApplicationListener、FailureAnalyzer 这类扩展点,SpringBoot 3.0 给了两种迁移方式,选哪种都方便:
方式一:用对应 imports 文件(官方推荐)
比如迁移 ApplicationListener,就建个 META-INF/spring/ApplicationContextInitializer.imports 文件,把实现类的全限定名写进去就行:
com.example.MyApplicationListener
方式二:用 @Bean 注册(自己写的扩展用这个更灵活)
直接在配置类里用 @Bean 把扩展点实例注册进去,连配置文件都省了,特别适合自定义的场景:
@Configuration
public class ListenerConfig {
@Bean
public MyApplicationListener myApplicationListener() {
return new MyApplicationListener();
}
}
4.3 自定义扩展点:自己写的 Loader 咋改?
如果项目里自己搞了基于 spring.factories 的扩展(比如自定义了 Loader 类),也不用慌,把加载逻辑从“读 spring.factories”改成“读自定义 imports 文件”就行。看下面的示例:
老逻辑(靠 spring.factories):
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
// 加载spring.factories中的MyExtension实现
return SpringFactoriesLoader.loadFactories(MyExtension.class, null);
}
}
新逻辑(靠自定义 imports 文件):
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
// 1. 加载自定义的MyExtension.imports文件
List<String> classNames = loadFromImportsFile("META-INF/spring/MyExtension.imports");
// 2. 实例化类(可复用Spring的工具类)
return classNames.stream()
.map(className -> ClassUtils.forName(className, null).newInstance())
.collect(Collectors.toList());
}
}
五、小插曲:SpringFactoriesLoader 也升级了
为了配合新机制,SpringBoot 3.0 也给 SpringFactoriesLoader 这个类做了大升级,同时还留了点兼容性支持,怕咱们一下子适应不过来。
5.1 核心 API 变了:更灵活了
原来的核心方法 loadFactories 被标为过时了,新增了一个 loadFactoryNames 方法,只负责“读类名”,实例化的事儿交给咱们自己,这样用起来更灵活:
public final class SpringFactoriesLoader {
// 过时方法:同时完成类名加载和实例化
@Deprecated
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { ... }
// 新方法:仅加载类名,实例化由调用方控制
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 内部已适配imports文件加载逻辑
...
}
}
5.2 兼容性很贴心:老项目不用慌
为了不让老项目直接崩掉,SpringBoot 3.0 对 EnableAutoConfiguration 这类核心扩展点,还保留着解析 spring.factories 的能力。但官方说了,新项目一定要用 imports 文件,老项目也赶紧迁,以后版本里这部分兼容代码就会删掉了。
六、最后聊聊:这场变革背后的小道理
spring.factories 退场,不是它不好,而是技术在进步,它跟不上 GraalVM 、模块化这些新需求了。这本质上是 SpringBoot 在“性能优化”“适配新生态”“支持云原生”这三大方向上的必然选择——老伙计完成了它的使命,该给新方案让位置了。
新的 imports 文件机制,凭着“静态化、分类化、高效化”这三大优势,不仅解决了老问题,还为 SpringBoot 在云原生时代的发展铺好了路。对咱们开发者来说,不用死记硬背迁移步骤,搞懂背后“适配新生态、提升效率”的逻辑,就能明白这场变革的意义,写代码也能更贴合框架的发展方向。
👉 欢迎加入小哈的星球,你将获得: 专属的项目实战(多个项目) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
-
新项目:《Spring AI 项目实战》正在更新中..., 基于 Spring AI + Spring Boot 3.x + JDK 21;
-
《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍;演示地址:http://116.62.199.48:7070/
-
《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;
-
专栏阅读地址:https://www.quanxiaoha.com/column
截止目前,累计输出 100w+ 字,讲解图 4013+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有4000+小伙伴加入


1. 我的私密学习小圈子,从0到1手撸企业实战项目~ 2. 天才用户取用户名为null,害我熬夜查到两点钟。。 3. 别再只会 mvn install 了!深入拆解 Maven 插件核心原理 4. MinIO 不再“开放”,RustFS 能否成为更优选择?
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。 获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。 点“在看”支持小哈呀,谢谢啦
更多推荐

所有评论(0)