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 新方案的四大亮点,用了都说好

  1. 启动更快:需要啥扩展点就加载啥文件,不用再扫所有 JAR 包的单个文件,无效 IO 直接减少。

  2. 跟模块系统处得好:通过 Java 模块的“opens”声明把 META-INF/spring 目录暴露出去,完全符合 JPMS 的要求。

  3. 配置超简单:扔了老机制的键值对,改成“一行一个类名”,不用记复杂格式,写起来不容易错。

  4. 适配原生镜像无压力:静态的文件结构和类名,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 最常用场景:自动配置类迁移

这是咱们最常碰到的情况,三步走就能搞定:

  1. 第一步:代码不用改,直接留着:原来的 @Configuration、@ConditionalOnXxx 这些注解都不用动,代码完全复用。比如这个配置类:@Configuration @ConditionalOnClass(Foo.class) @EnableConfigurationProperties(FooProperties.class) public class FooAutoConfiguration {  // 配置逻辑一点不变 }

  2. 第二步:新建 imports 文件:在 resources 目录下,先建 META-INF/spring/ 这个目录,然后在里面新建一个文件,名字固定叫 org.springframework.boot.autoconfigure.AutoConfiguration.imports。

  3. 第三步:写类名:在刚建的文件里,一行写一个自动配置类的全限定名就行,比如: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:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐