首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >SpringBoot 挥别 spring.factories:老伙计退场,新方案真香!

SpringBoot 挥别 spring.factories:老伙计退场,新方案真香!

作者头像
java金融
发布2025-11-13 17:49:59
发布2025-11-13 17:49:59
1790
举报
文章被收录于专栏:java金融java金融

SpringBoot 3.0 发布时,干了一件挺“大事”——把陪伴咱们多年的 spring.factories 给“请”下了台。这波操作引发了不少开发者的讨论,有人觉得迁移费点劲,有人用了新方案后直呼“舒服”。今天咱们就来好好聊聊,这个老伙计为啥会被替代,新方案又好在哪儿,以及怎么顺顺利利完成迁移。

一、先聊聊老熟人:spring.factories 是啥?

在说它“退休”的事儿之前,咱们得先搞明白,这个 spring.factories 到底在 SpringBoot 生态里扮演啥角色。其实它不是 SpringBoot 凭空造的,而是 Java SPI 机制的“变种选手”,说白了就是 SpringBoot 自动配置的“幕后推手”,没它,很多“开箱即用”的功能都玩不转。

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 的核心代码感受下:

代码语言:javascript
复制

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):又长又容易错

代码语言:javascript
复制

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfiguration

新写法(AutoConfiguration.imports):清爽到飞起

代码语言:javascript
复制

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 文件,把实现类的全限定名写进去就行:

代码语言:javascript
复制

com.example.MyApplicationListener

方式二:用 @Bean 注册(自己写的扩展用这个更灵活)

直接在配置类里用 @Bean 把扩展点实例注册进去,连配置文件都省了,特别适合自定义的场景:

代码语言:javascript
复制

@Configuration
public class ListenerConfig {
    @Bean
    public MyApplicationListener myApplicationListener() {
        return new MyApplicationListener();
    }
}

4.3 自定义扩展点:自己写的 Loader 咋改?

如果项目里自己搞了基于 spring.factories 的扩展(比如自定义了 Loader 类),也不用慌,把加载逻辑从“读 spring.factories”改成“读自定义 imports 文件”就行。看下面的示例:

老逻辑(靠 spring.factories):

代码语言:javascript
复制

public class MyExtensionLoader {
    public List<MyExtension> loadExtensions() {
        // 加载spring.factories中的MyExtension实现
        return SpringFactoriesLoader.loadFactories(MyExtension.class, null);
    }
}

新逻辑(靠自定义 imports 文件):

代码语言:javascript
复制

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 方法,只负责“读类名”,实例化的事儿交给咱们自己,这样用起来更灵活:

代码语言:javascript
复制

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 在云原生时代的发展铺好了路。对咱们开发者来说,不用死记硬背迁移步骤,搞懂背后“适配新生态、提升效率”的逻辑,就能明白这场变革的意义,写代码也能更贴合框架的发展方向。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java金融 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、先聊聊老熟人:spring.factories 是啥?
    • 1.1 它在哪儿?能干啥?
    • 1.2 它的工作日常:启动时忙啥呢?
  • 二、为啥要跟老伙计说再见?spring.factories 的那些槽点
    • 槽点1:启动慢如蜗牛——全量扫描的效率坑
    • 槽点2:跟 Java 9+ 处不来——模块化的兼容难题
    • 槽点3:先加载再筛选——白费力气的笨办法
    • 槽点4:配置散满天——维护起来头都大
    • 槽点5:跟 GraalVM 不兼容——原生镜像的拦路虎
  • 三、新方案登场:imports 文件凭啥“接棒”?
    • 3.1 新方案的核心思路:按功能“分文件”
    • 3.2 新方案的四大亮点,用了都说好
    • 3.3 新旧对比:一眼看出新方案多香
      • 老写法(spring.factories):又长又容易错
      • 新写法(AutoConfiguration.imports):清爽到飞起
  • 四、实战教学:手把手教你从老的换成新的
    • 4.1 最常用场景:自动配置类迁移
    • 4.2 其他扩展点:比如 Listener、Analyzer
      • 方式一:用对应 imports 文件(官方推荐)
      • 方式二:用 @Bean 注册(自己写的扩展用这个更灵活)
    • 4.3 自定义扩展点:自己写的 Loader 咋改?
      • 老逻辑(靠 spring.factories):
      • 新逻辑(靠自定义 imports 文件):
  • 五、小插曲:SpringFactoriesLoader 也升级了
    • 5.1 核心 API 变了:更灵活了
    • 5.2 兼容性很贴心:老项目不用慌
  • 六、最后聊聊:这场变革背后的小道理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档