Spring Boot 自动配置迁移:从 spring.factories 到 AutoConfiguration.imports 完整指南
用 Spring Boot 干活儿的人多多少少都见过META-INF/spring.factories这个文件。早年间自己做 starter 或者封装公司内部组件的时候都是靠它那一行org.springframework.boot.autoconfigure.EnableAutoConfigurationxxx把自己的配置类塞进 Spring Boot 的自动配置流程里只要把 jar 丢进 classpath配置类就能被自动加载确实省了不少事。但是从 Spring Boot 2.7 开始官方推出了一个新文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports来替代 spring.factories 在自动配置这部分的职责Spring Boot 3.x 里更干脆自动配置这块只认 imports 文件旧的 spring.factories 写法直接失效。这个变化看着不大实际踩坑的人特别多有的组件升级后配置死活不生效有的 starter 要同时兼容 2.x 和 3.x 结果两头难搞。这篇文章就把这两份文件的来龙去脉、迁移方法和常见坑一次性说清楚做过 starter、中间件封装或者正在升级 Spring Boot 老项目的同学都能直接参考。1. 先从 spring.factories 说起它到底干了什么1.1 spring.factories 文件的本来面目spring.factories是 Spring Boot 里一个历史悠久的配置文件本质上是一个 Properties 格式的文件放在 jar 包里的META-INF/spring.factories路径下。它的原理不复杂Spring Boot 启动的时候会通过SpringFactoriesLoader去扫描 classpath 下所有 jar 里的这个文件然后把里面按 key 分组的 value 全部加载出来实例化并缓存起来。常见的用法是给org.springframework.boot.autoconfigure.EnableAutoConfiguration这个 key 配置多个自动配置类多个类用逗号分隔org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.demo.config.MyAutoConfiguration,\ com.example.demo.config.MyOtherAutoConfiguration除了自动配置spring.factories还能挂载其他类型的 SPI比如org.springframework.context.ApplicationContextInitializercom.example.demo.config.MyContextInitializer org.springframework.context.ApplicationListenercom.example.demo.config.MyApplicationListener org.springframework.boot.env.EnvironmentPostProcessorcom.example.demo.config.MyEnvPostProcessor也就是说spring.factories是个多用途文件。Spring Boot 内部很多 SPI 机制的默认实现像EnvironmentPostProcessor、ApplicationContextInitializer、ApplicationListener、SpringApplicationRunListener等等全都是通过它注册的。这也是它后来被拆分的原因之一。1.2 一个文件身兼数职的历史包袱问题就出在“多用途”这三个字上。org.springframework.boot.autoconfigure.EnableAutoConfiguration这个 key 只是spring.factories支持的众多 key 之一但实际使用中它恰恰是被引用得最频繁、内容量最大的一个。你去翻那些成熟的中间件 jar比如 MyBatis、Redis、消息队列的 starter里面的spring.factories动辄就是几十行自动配置类一个文件里既塞自动配置又塞监听器维护起来很痛苦。更麻烦的是加载方式。SpringFactoriesLoader底层用的是PropertiesLoaderUtils把整个文件当 Properties 解析要加载某一个 SPI 就得先把整个文件读出来。Spring Boot 2.x 时代启动期为了找自动配置类会把 classpath 下几百个 jar 的spring.factories全部扫描一遍字符串拼接、去重、类加载全都挤在启动路径上。虽然 Spring Boot 2.4 之后做了按需加载的优化启动速度比早期版本快了但底层这套“一个大文件管所有事”的设计依然对启动性能和代码可维护性都不友好。还有一个容易被忽略的问题spring.factories是统一格式的 key-value 结构value 是一长串逗号分隔的类名写错了很难一眼看出来而且没有天然的“一个类一行”的阅读体验code review 的时候密密麻麻一片非常不直观。2. AutoConfiguration.imports 是怎么取代它的2.1 新文件长什么样AutoConfiguration.imports的完整路径是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。注意这个路径里的org.springframework.boot.autoconfigure.AutoConfiguration并不是随便起的名字它对应 Spring Boot 的AutoConfiguration注解类也就是说这个文件专门用来注册带AutoConfiguration注解的配置类。文件内容格式很简单一个类名占一行支持#注释com.example.demo.config.MyAutoConfiguration com.example.demo.config.MyOtherAutoConfiguration # 上面两个类就是本 starter 的核心自动配置没有 key没有冒号等号不需要逗号分隔多行排列一眼就能看清楚。Spring Boot 加载它的时候会通过内部的ImportCandidates机制扫描所有 jar 中特定路径下的同名文件把每一行内容作为自动配置类名加载进来。2.2 官方为什么非要换这一下说到底就两个词解耦和性能。先说解耦。spring.factories把自动配置和其他 SPI 混在一起Spring Boot 团队想让自动配置这块职责更单一。拆出AutoConfiguration.imports后spring.factories继续保留给ApplicationContextInitializer、ApplicationListener、EnvironmentPostProcessor这些非自动配置的 SPI 用两类机制各归各互不干扰。以后看一个 jar 里有没有自动配置直接看META-INF/spring/下有没有那个 imports 文件就行了定位问题也快。再说性能。新文件的解析比 Properties 解析快得多不需要处理转义、不需要扫描所有 key文件目标明确类名直接按行读。Spring Boot 团队在 2.7 的 release notes 里明确说了这个改动可以避免在每次启动时处理大量无用的 spring.factories 条目。实测从 2.6 升到 2.7 再升到 3.x启动时间确实有可感知的下降尤其微服务模块多的时候累积下来的收益很明显。另外还有一个安全角度的考虑。spring.factories用 Properties 格式值里面可以塞各种转义字符解析逻辑复杂历史上出现过一些奇怪的解析问题。改成纯文本按行读取的 imports 格式解析逻辑显著简化也少了一些可以被利用的边界情况。2.3 两个机制背后的加载原理差异要理解两者的区别得落到源码上看。Spring Boot 2.7 之前自动配置类的候选名单是通过AutoConfigurationImportSelector.getCandidateConfigurations()方法拿到的protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { ListString configurations new ArrayList( SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)); // ... }核心就是SpringFactoriesLoader.loadFactoryNames()它扫的就是所有 jar 里的META-INF/spring.factories取EnableAutoConfiguration这个 key 对应的 value。Spring Boot 2.7 开始这个方法改成了这样protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { ListString configurations ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader) .getCandidates(); // ... }ImportCandidates.load()扫的路径是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。在 2.7 里为了兼容老项目两个来源的结果都会收集并去重同时启动时会打一条 deprecation 警告提示你spring.factories里配置的自动配置类要迁移到 imports 文件。等到了 Spring Boot 3.0spring.factories里的EnableAutoConfiguration这个 key 就完全被忽略了只剩 imports 文件生效。这里还有一个容易踩的细节ImportCandidates.load()要求 imports 文件的位置必须固定文件名完全一致一个字母都不能差。所以迁移的时候路径一定要写对我见过有人把文件名简写成AutoConfiguration.imports放在META-INF/spring/下结果 Spring Boot 压根不认配置类全都没加载排查了半天才发现是文件名少了org.springframework.boot.autoconfigure.这个前缀。3. 实际操作从 spring.factories 平滑迁移到 imports 文件3.1 迁移前先梳理自己的代码在动手改文件之前先把项目里所有用到spring.factories的地方列出来。因为spring.factories不只是给自动配置用如果你在里面配了ApplicationListener或EnvironmentPostProcessor这几个 key 在 Spring Boot 3.x 里依然有效不能一刀切删掉。建议先做一张清单区分两类条目spring.factories 里的 keySpring Boot 3.x 是否继续支持处理方式org.springframework.boot.autoconfigure.EnableAutoConfiguration不支持迁移到 AutoConfiguration.importsorg.springframework.context.ApplicationContextInitializer支持保留org.springframework.context.ApplicationListener支持保留org.springframework.boot.env.EnvironmentPostProcessor支持保留org.springframework.boot.SpringApplicationRunListener支持保留还有一个需要留意的就是自己项目里是不是有第三方老依赖还在用spring.factories注册自动配置这个属于外部依赖问题后面单独讲。3.2 新文件的格式与注意点新建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports把原来spring.factories里EnableAutoConfiguration下面的类名逐行搬进去。比如原来是这样org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.demo.config.MyAutoConfiguration,\ com.example.demo.config.MyOtherAutoConfiguration迁移后 imports 文件就是com.example.demo.config.MyAutoConfiguration com.example.demo.config.MyOtherAutoConfiguration就这么简单表面上是的但有几个细节必须要说清楚。第一imports 文件里的行尾不能有空格Spring Boot 解析的时候会做 trim实际带了空格也能跑但仓库里带着尾随空格会被 review 的人念叨。第二空行和#开头的注释行会被忽略这一点和 Properties 文件不一样注释只能整行写。第三同一个类名如果在多个 jar 里出现了比如两个依赖都提供了相同的 imports 文件路径和相同类名Spring Boot 做过去重处理不会导致重复注册但会产生难以定位的优先级问题这个在常见问题里再展开。3.3 分批迁移加回归验证我的建议是不要在一个大版本升级里同时做“换文件 升级 Spring Boot”最好分两步。如果你的项目正在 Spring Boot 2.6可以先把 Spring Boot 升级到 2.7.x然后迁移spring.factories这时候两种机制并存即使迁移出了问题Spring Boot 2.7 也会回退读取 spring.factories 里的自动配置只是给个警告而已风险非常低。等确认 imports 文件生效、配置类都正常加载了再继续升到 3.x。具体验证方法也很简单先看启动日志里有没有类似The following auto-configurations were considered的段落确认自己的类出现在自动配置评估报告里并且没标negative。如果你是 actuator 用户更直接的办法是把org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener的日志级别调成 DEBUG启动后会打印一份完整的条件评估报告你的自动配置类为什么匹配了、为什么没匹配一目了然。4. 双版本兼容一个组件同时兼容 Spring Boot 2.x 和 3.x4.1 打包时同时保留两份文件如果你的 starter 是给别人用的公共组件现在大概率还处在“客户既有 2.x 也有 3.x”的阶段那就需要两份文件同时存在spring.factories 保留给 2.ximports 文件提供 3.x 的支持。需要注意Spring Boot 2.7 和 3.x 读取 imports 文件的时机都早于spring.factories而且 2.7 对同一自动配置类会做去重。所以同时存在两份文件并且类名一致时2.7 项目里不会重复注册单纯从兼容性角度是安全的。真正的问题在别处如果你的 starter 引用了javax还是jakarta命名空间的 API在 3.x 下会直接加载失败这个和文件格式无关属于 Java EE 到 Jakarta EE 迁移的大范围问题。组件要做双版本兼容通常得拆成两个 artifact或者做一遍javax到jakarta的条件替换目前没有银弹。4.2 别忘了给自动配置类加 AutoConfigurationAutoConfiguration.imports文件里的配置类强烈建议加上AutoConfiguration注解。这个注解从 Spring Boot 2.7 开始提供本身等同于Configuration(proxyBeanMethods false)并且专门用于标识这是一个自动配置类。有个容易被忽略的细节AutoConfiguration注解上是可以配after、before属性的用来声明自动配置的加载顺序比如AutoConfiguration(after DataSourceAutoConfiguration.class) public class MyBatisCustomAutoConfiguration { }这比在类上单独写AutoConfigureAfter更明确。不过我要提醒一句AutoConfiguration的after、before属性只是用于自动配置排序千万别把它和 Spring 的DependsOn混为一谈两者解决的问题完全不同。如果代码里还在用Configuration也能放进 imports 文件Spring Boot 2.7/3.x 不会强制要求必须是AutoConfiguration但我个人建议统一改成AutoConfiguration一方面是表达意图另一方面是AutoConfiguration默认proxyBeanMethods false启动阶段不用给配置类生成 CGLIB 代理能省一点开销。配置类里要是用到Bean方法互相调用的场景就需要改成proxyBeanMethods true或者干脆把互相调用的Bean抽成单独类这点在 3.x 里尤其要小心。4.3 条件注解才是兼容性的核心双版本兼容还有一个绕不开的话题就是条件注解。ConditionalOnClass、ConditionalOnMissingBean这些注解在 3.x 里依然能用但要注意它们处理的是当前 classpath 下的类。举例来说你的自动配置类里引用了javax.sql.DataSourceSpring Boot 3.x 项目里如果没有这个类条件匹配会失败。更隐蔽的是ConditionalOnClass(name javax.servlet.Servlet)这种按类名判断的写法因为 3.x 里 Servlet API 换了jakarta.servlet的包名类名匹配不上自动配置被跳过看起来就是“代码没报错但功能没生效”。这种问题在日志里只显示negative match排查起来很费劲。做双版本兼容时建议优先用接口或抽象类实现类加载避免在自动配置类里直接依赖具体的 servlet 容器类。5. 常见问题与排查技巧实录5.1 自动配置类不生效第一步查什么这是迁移后问得最多的问题。遇到“我明明把类写进 imports 文件了但项目跑起来后配置没生效”先按这个顺序查确认AutoConfiguration.imports文件路径拼写完全正确META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports少一个.都不行。确认文件在编译后真的进入了 jar。很多项目用 maven 时把资源文件放在src/main/resources下如果构建配置里对资源文件做了 filter 或者 exclude这个文件可能根本不在最终 jar 里。用jar tf或者unzip -l直接看产物。确认自动配置类上的条件注解是否满足。最常见的是ConditionalOnClass里指定的类不在 classpath 中或者类名在 3.x 下因为包名迁移问题匹配不到。看启动日志里的条件评估报告找到你的配置类名看它到底是matched还是negative。这几个步骤能筛掉九成的问题剩下的基本都是顺序问题或重复注册问题。5.2 类被重复加载或顺序不对前面说了2.7 里spring.factories和 imports 文件同时存在时自动配置类会去重但如果你的 starter 里写了两个自动配置类一个在 spring.factories 里另一个在 imports 文件里而它们之间有先后依赖关系加载顺序可能和预期完全不同。自动配置的顺序由三类机制控制AutoConfigureOrder、AutoConfigureBefore、AutoConfigureAfter以及AutoConfiguration注解的before/after属性。在迁移时原来 spring.factories 里的顺序信息是通过AutoConfigurationImportSelector读取的imports 文件本身不包含任何顺序信息。所以迁移之后如果发现顺序乱了不要把精力花在调文件顺序上而是去检查类上有没有把AutoConfigureAfter、AutoConfigureBefore标清楚。顺序声明是类的属性跟它写在哪个文件里没有关系。5.3 第三方老依赖把旧机制带回来了有一种情况特别折磨人你的代码已经全部迁移到 imports 文件了但启动时还是看到某个自动配置类被加载了两次或者看到The following auto-configurations were considered里有别人的旧类。这时候问题多半出在间接依赖上。某个老版本中间件 jar 里依然自带META-INF/spring.factories而它的传递依赖又没有被 exclude 掉。Spring Boot 3.x 虽然不处理EnableAutoConfigurationkey 了但如果这个老 jar 是在 Spring Boot 2.x 的 starter 中间层里被引入的它的自动配置还是会被间接加载。排查方法是用依赖树功能把依赖关系捋一遍重点找那些带spring.factories的传递依赖升级或者 exclude 掉。5.4 编译期没报错但启动慢、日志乱还有一个容易被忽略的问题imports 文件里的类名写错Spring Boot 启动时会尝试加载这个类并抛出IllegalStateException或NoClassDefFoundError。因为自动配置加载发生在 Spring 容器创建早期这时候的异常信息往往很诡异不会直接告诉你“哪个类找不到”。遇到启动报错但堆栈指向容器初始化、无法定位到具体配置类的情况优先去看 jar 里 imports 文件有没有类名写错、类名里有没有包含空格或特殊字符。提示尽可能让 imports 文件里的排列顺序与类之间的依赖顺序保持一致。虽然最终执行顺序由注解控制但保持一致能让排查问题的人省很多脑力。6. 迁移时的几个实操心得最后分享几条我个人做组件迁移时的真体验。第一spring.factories改成AutoConfiguration.imports这事看着是个纯文件搬运实际上最容易出问题的是“你以为搬完了其实还有别的地方在管”。比如有的项目把自动配置类写在了业务代码模块里靠的是ComponentScan扫描这种跟 spring.factories 半毛钱关系没有别被迁移带偏了。第二不要迷信“只要文件路径对了就万事大吉”记得在 CI 里加一个启动冒烟测试用最小的 Spring Boot 3.x 工程引用你的组件跑一个空上下文验证自动配置真的加载了。这个成本极低但能挡住大部分低级错误。第三如果你维护的是开源组件迁移的时候记得在 README 里明确写一句“支持 Spring Boot 2.7 / 3.x”很多用户遇到配置不生效第一时间根本不会想到是版本问题。还有一个小细节值得注意同一份 imports 文件可以被多个 jar 共用也可以在一个 jar 里同时出现多次不建议这么玩。保持每个 jar 只提供一份META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports所有自动配置类集中放在同一份文件里规则清晰review 也方便。注意Spring Boot 3.x 里spring.factories并不是完全消失了ApplicationListener、EnvironmentPostProcessor这些 SPI 还是走老文件。如果你在迁移时把整个 spring.factories 文件删了这些 SPI 也会跟着失效到时候启动直接少了监听器问题比自动配置不生效更难查。正确做法是只把EnableAutoConfiguration那一节拆出来其他条目保持不动。从我个人的项目经历来看这个迁移最大的价值其实不是启动性能提升而是把自动配置的注册入口变得单一、直观。以前在spring.factories里堆一长串类名看的人头皮发麻现在 imports 文件一行一个类代码审查的时候扫一眼就知道这个组件暴露了哪些自动配置。如果一个组件连自动配置清单都列不清楚那说明它的职责边界本身就有问题。反过来说能把这份 imports 文件整理得干干净净的项目架构通常也不会太差。这份经验其实可以继续往深了挖比如写一个自定义的AutoConfigurationImportFilter来控制某些自动配置在特定环境下的加载或者用AutoConfigureOrder精确调整多个组件之间的初始化时机这些都是后话了先把这次的迁移做好后面这些机制自然就顺手了。

相关新闻

最新新闻

日新闻

周新闻

月新闻