ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot自动配置失效?从spring.factories到AutoConfiguration.imports迁移指南

Spring Boot自动配置失效?从spring.factories到AutoConfiguration.imports迁移指南 接手老项目时最怕什么怕它升级完Spring Boot之后自定义的starter悄无声息地失效。我有一次就栽在这个上面项目从Spring Boot 2.5一路升到3.1业务跑起来才发现自动配置根本没生效翻了半天源码最后定位到原因——自动配置类的注册位置变了老的spring.factories写法在3.x里已经被移除了。今天就把这事彻底讲清楚spring.factories和org.springframework.boot.autoconfigure.AutoConfiguration.imports到底是怎么回事为什么官方要搞出后面这个新文件以及你在做版本迁移、写自定义starter时应该怎么改、怎么排查。这篇文章适合三类人看一是正在做Spring Boot版本升级、遇到了自动配置失效问题的同学二是想写自己的starter、但对自动配置的加载链路还一知半解的三是纯粹想搞懂Spring Boot内部机制、不想只停留在“会用注解”层面的开发者。整个思路我会从原理讲到实操最后附上我踩过的坑和排查清单保证你看完能直接照着操作。1. 自动配置是怎么被发现的一条链路讲清楚1.1 自动配置的本质启动时读一张“候选清单”先说个最基础的问题。Spring Boot能“少配置、开箱即用”靠的是自动配置。但自动配置类并不是被Spring容器像普通Component那样扫描出来的它是被一个专门的导入器在启动时统一加载的。这条链路大致是这样的主类上的SpringBootApplication点开之后里面有个EnableAutoConfiguration。EnableAutoConfiguration往里走核心是一个叫AutoConfigurationImportSelector的类。这个Selector在selectImports()方法里会去读取classpath下所有jar包里“自动配置候选清单”文件然后拿到一串自动配置类的全限定名。拿到名字之后再逐个评估条件注解比如ConditionalOnClass、ConditionalOnMissingBean这些满足条件的才会被真正注册成Bean。重点来了候选清单文件在Spring Boot 2.7之前和2.7之后是两种完全不同的文件。老的叫META-INF/spring.factories新的叫META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。这就是标题里两个名字的由来。前者是历史遗产后者是新一代标准。1.2 spring.factories 的老规矩一个文件管所有扩展点spring.factories是Spring Boot很早之前就引入的“万能配置入口”。它本质上就是一个标准的Properties格式文件以键值对形式存在里面可以注册很多种扩展接口的实现类。自动配置只是它管理的众多扩展点中的一个。换句话说spring.factories里可以同时写好几类key最常见的包括org.springframework.boot.autoconfigure.EnableAutoConfiguration注册自动配置类。org.springframework.boot.env.EnvironmentPostProcessor自定义环境处理逻辑。org.springframework.context.ApplicationContextInitializer容器初始化回调。org.springframework.context.ApplicationListener应用事件监听器。org.springframework.boot.SpringApplicationRunListenerSpringApplication运行监听器。举个老项目里常见的写法org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.demostarter.DemoAutoConfiguration,\ com.example.demostarter.CacheAutoConfiguration org.springframework.context.ApplicationListener\ com.example.demostarter.StartupListener在Spring Boot 2.6及更早版本里Spring Boot就是通过SpringFactoriesLoader.loadFactoryNames()这个方法读所有jar包META-INF/spring.factories下的这个key拼出自动配置候选类的完整列表。这套机制本身没什么问题简单直接一看就懂。但它有一个天生的弱点这个文件承担了太多职责。你打开一个starter的spring.factories里面可能同时混着自动配置、监听器、初始化器好几类条目大家共用一个Properties格式没有专门的语法校验拼写错了不报错只会静默失效。1.3 AutoConfiguration.imports 的新规矩一个文件只管一件事Spring Boot 2.7开始官方引入了一个全新的文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。路径很长但结构反而极其简单。这个文件只有一个规则一行一个自动配置类的全限定名支持#开头的注释行。比如com.example.demostarter.DemoAutoConfiguration com.example.demostarter.CacheAutoConfiguration注意它不再需要keyvalue这套格式也不用管Properties转义、反斜杠续行这些乱七八糟的东西。打开文件一目了然里面只有自动配置类别的什么都不管。这个文件的加载逻辑也很直接。Spring Boot的AutoConfigurationImportSelector会通过SpringFactoriesLoader扫描classpath下所有META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件逐行读取类名组成自动配置候选列表。所以从2.7开始Spring Boot实际上会同时从两个地方读自动配置类老的spring.factories里的EnableAutoConfigurationkey和新的AutoConfiguration.imports文件。两者合并处理。Spring Boot 3.x之后老路径被彻底移除了只剩新文件这一条路。这两者的对比我整理成了一个表方便大家记忆对比项spring.factoriesAutoConfiguration.imports文件路径META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件格式Properties键值对纯文本一行一个类名是否区分自动配置与其他扩展点不区分混在一起专门服务自动配置Spring Boot 2.6及以下支持不支持Spring Boot 2.7 - 2.x支持支持Spring Boot 3.x自动配置不再支持唯一标准拼错类名时静默忽略同样静默忽略但对内容更友好2. 为什么官方要换掉 spring.factories不只是一个格式问题2.1 职责混杂是最大的“坏味道”很多人以为这次切换只是为了换个好看的格式其实不是。核心原因是spring.factories把好几类完全不相干的东西塞在同一个文件里导致它同时被好几套不同的加载机制读取。比如SpringFactoriesLoader.loadFactories()加载的是所有扩展点列表而自动配置的加载又需要经过AutoConfigurationImportSelector做条件评估。当两类逻辑共用同一个文件、同一套key-value格式时就出现了一个很别扭的问题自动配置类越多spring.factories就越臃肿维护成本越高。我自己维护过几个老starter有段时间打开spring.factories就头疼。上面是EnableAutoConfiguration的条目下面是ApplicationListener的条目中间还夹着FailureAnalyzer改动的时候得反复确认没改错段落。到了Spring Boot 2.7引入imports文件之后自动配置类从spring.factories里拆出去这个文件瞬间清爽多了剩下的每一个key都对应一个独立的职责找起来也快。这里多说一句spring.factories这个文件本身并不会彻底消失。在Spring Boot 3.x里ApplicationListener、ApplicationContextInitializer、EnvironmentPostProcessor这些扩展点仍然要走spring.factories。被移除了的仅仅是EnableAutoConfiguration这个key的注册能力。这一点很多人容易搞混以为是Spring Boot 3把整个spring.factories干掉了其实不是。2.2 减少“解析一份文件要做的无用功”另一个容易被忽视的原因是解析效率。老的spring.factories是Properties格式读取时要按keyvalue解析遇到多行用反斜杠续行的长列表还得做字符串处理。而自动配置类列表的特点是内容大、格式固定、不需要key。用Properties格式去存一个纯列表本身就是拿大炮打蚊子。换成AutoConfiguration.imports后一行一个类名读文件的代码变得非常简单按行读去掉注释就得到一个干净的List。逻辑简单了出错概率自然下降。当然从性能角度说启动阶段这个读取消耗本身微乎其微真要较真那不是主要矛盾。但是“用最简单的结构承载最通用的场景”这是Spring Boot作为框架在长期演进里坚持的方向这点我觉得值得学习。2.3 注解风格跟着一起进化AutoConfiguration和AutoConfiguration.imports文件配套的还有一个新注解AutoConfiguration。以前注册一个自动配置类最标准的写法是Configuration(proxyBeanMethods false) AutoConfigureBefore(SomeAutoConfiguration.class) AutoConfigureAfter(AnotherAutoConfiguration.class) public class DemoAutoConfiguration { // ... }这里有两个烦的地方一是每次都记得写proxyBeanMethods false虽然Spring Boot自己的配置类都这么写但手写时容易漏漏了也不报错就是产生不必要的CGLIB代理开销二是前置、后置、顺序这三个注解每次都得一字排开看着累。AutoConfiguration直接把这些打包了AutoConfiguration( before SomeAutoConfiguration.class, after AnotherAutoConfiguration.class ) public class DemoAutoConfiguration { // ... }它内部已经固定搭配了Configuration(proxyBeanMethods false)B格也更高。在Spring Boot 2.7和2.x版本里Configuration和AutoConfiguration都可以用来标注自动配置类但到了3.x自动配置类再用Configuration就不合适了官方建议统一换成AutoConfiguration。用新注解还有一个隐藏的好处它自带before和after属性把排序信息直接写在注解声明里比散落三个注解更聚合。这一点在做复杂starter之间的依赖编排时体验差别特别明显。3. 实操自定义starter从旧方案迁移到新方案3.1 先确认你的Spring Boot版本再动手迁移前第一件事确认项目当前用的Spring Boot版本这直接决定你可以怎么操作。判断标准其实很简单如果项目是Spring Boot 2.6及以下建议先升级到2.7.x因为2.7同时兼容新旧两套机制迁移过程最平滑。你可以在2.7下把自动配置切到新文件跑通之后再考虑升3.x。如果项目是Spring Boot 2.7到2.9之间直接动手迁移就行老文件还能继续用就算新文件有问题老配置还在不会彻底启动失败。如果项目已经升到Spring Boot 3.x别犹豫了自动配置必须迁移不迁移就是不生效。检查项目版本一行命令就够mvn help:evaluate -Dexpressionproject.parent.version -q -DforceStdout或者直接在pom.xml里看spring-boot-starter-parent的version。要是项目用的是BOM管理而非parent继承就去dependencyManagement里找。这里提醒一句如果你的项目是多个module组成的starter那要注意新文件应该放在“自动配置模块”的资源目录下不是放在业务api模块里。比如starter拆成demo-starter-api和demo-starter-autoconfigure两个模块那AutoConfiguration.imports应该放后者。3.2 迁移四步走从增删文件到替换注解我先把完整迁移步骤写出来再逐个解释为什么。第一步新建imports文件在src/main/resources下建目录META-INF/spring然后新建文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports注意文件名是一整段不是目录。这里初学者容易看懵以为是两层目录套一个文件。实际上org.springframework.boot.autoconfigure.AutoConfiguration.imports是整个文件的名字前面那个META-INF/spring/才是目录路径。第二步把spring.factories里的自动配置条目搬进来打开老的META-INF/spring.factories找到org.springframework.boot.autoconfigure.EnableAutoConfiguration这一行把后面的所有类名拷贝到新建的imports文件里每个类名独占一行。举例来说老文件里如果写的是org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.demostarter.DemoAutoConfiguration,\ com.example.demostarter.CacheAutoConfiguration那新文件里就写成com.example.demostarter.DemoAutoConfiguration com.example.demostarter.CacheAutoConfiguration第三步把自动配置类上的Configuration换成AutoConfiguration这个是推荐的尤其在目标版本是3.x时更是必须的。找到所有被imports文件引用的类把类头上的Configuration(proxyBeanMethods false)换成AutoConfiguration。如果原来还有AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder注解保留它们或者把它们合并进AutoConfiguration的before、after属性里。这是迁移后的完整示例AutoConfiguration ConditionalOnClass(name com.example.demolib.DemoClient) EnableConfigurationProperties(DemoProperties.class) public class DemoAutoConfiguration { Bean ConditionalOnMissingBean public DemoClient demoClient(DemoProperties properties) { return new DemoClient(properties.getUrl()); } }第四步清掉spring.factories里的自动配置段落确认新文件工作正常后删除spring.factories里org.springframework.boot.autoconfigure.EnableAutoConfiguration这个key及其对应的类列表。保留其他key比如ApplicationListener、EnvironmentPostProcessor的条目它们和自动配置无关原样留在原地就好。整套迁移不复杂但有一个点值得强调迁移不只是“换个文件抄一遍”而是要把自动配置类的注解风格同步替换。我在实际项目中见过有人只搬了类名注解一个没动结果在Spring Boot 3.x下启动时自动配置类因为仍用Configuration而绕过了新的校验逻辑功能本身正常但日志里报警告排查起来也麻烦。3.3 条件装配自动配置“智能开关”的核心上面示例里出现了ConditionalOnClass、ConditionalOnMissingBean这个必须单独说清楚因为它决定了你的自动配置到底什么时候生效。自动配置最怕什么怕“强行生效”。如果你写的starter里某个类依赖一个可选jar包但用户的项目里恰好没引入那自动配置类一加载就直接ClassNotFound整个启动就崩了。条件装配就是用来干这个的。三个最常用的条件注解我配合实际场景解释ConditionalOnClass当某个类存在于classpath时才生效。比如连接池自动配置只有用户引入了HikariCP的依赖这个DataSource的自动配置才注册。ConditionalOnMissingBean当容器中不存在某个Bean时才生效。这个特别适合“允许用户覆盖”的场景。比如你提供了一个默认的RestTemplate但用户自己配置了一个那你的就不注册了。ConditionalOnProperty当配置项满足某个值时生效。比如demo.client.enabledfalse整个自动配置直接跳过。写自动配置类时这三者经常组合使用。我自己习惯的模板是类上挂ConditionalOnClass做依赖兜底Bean方法上挂ConditionalOnMissingBean做覆盖兜底核心Bean的开关用ConditionalOnProperty暴露给用户。这样三层下来既保证了默认可用又给用户留了充分的定制空间。有个细节是给Spring Boot 3.x同学提个醒ConditionalOnClass注解的值官方建议用name xxx字符串方式而不是value XxxClass.class。原因很简单如果那个类本身不在classpath里JVM加载注解时直接报错压根走不到条件判断那一步。用字符串名字就算类不存在也不会导致类加载失败条件判断还能从容地返回false。4. 迁移踩坑实录与排查技巧速查表4.1 自动配置没生效先从四个方向查我自己在迁移过程中以及后来帮同事排查问题遇到过的大多数“配置不生效”都可以归类成下面四种情况。整理成一个速查表大家按顺序排查就行症状可能原因排查与解决启动后自定义Bean完全没有imports文件没被打进jar包用jar tf xxx.jar看文件是否在META-INF/spring/下启动报ClassNotFound自动配置类引用了可选依赖检查类上的ConditionalOnClass是否用字符串name方式启动不报错但日志里没有自动配置报告Spring Boot版本还是2.6及以下确认版本2.7才开始支持新文件自动配置注册了但Bean被自己的配置覆盖ConditionalOnMissingBean位置不对把该注解放到Bean方法上而非类级别第一条是最容易踩的。maven打包时如果resources过滤配置写得不严谨或者构建插件把META-INF直接排除掉了那文件就不会出现在最终jar里。查这个问题最快的方式jar tf target/demo-starter-1.0.0.jar | grep AutoConfiguration.imports如果执行完没有输出说明文件不在包里。这时先检查pom.xml里的resources配置别让它把META-INF/spring给过滤掉了。第二条看着像是依赖问题其实是条件注解使用问题。升级到Spring Boot 3.x之后类加载行为更严格之前很多“侥幸能跑”的代码现在会炸出来。用ConditionalOnClass(name ...)能避开绝大多数此类问题。第三条的坑在于很多人直接把Spring Boot版本从2.6升到3.1中间的2.7、2.9过渡版本完全没用过自然也不知道AutoConfiguration.imports的存在。如果某天你发现代码里只有spring.factories没有imports文件那说明自动配置链路已经在3.x下断了。4.2 开debug日志让Spring Boot把底牌亮给你遇到自动配置类加载失败别瞎猜先开debug。最简单的方式是在启动参数里加--debug或者在application.properties里写debugtrue。启动完成后日志里会出现一段自动配置报告长这样 CONDITIONS EVALUATION REPORT Positive matches: DemoAutoConfiguration ... Negative matches: CacheAutoConfiguration Did not match: - ConditionalOnClass did not find required class com.example.cache.CacheManager这个报告是排雷神器。Positive matches说明哪些自动配置生效了Negative matches说明哪些没生效以及没生效的原因是什么。每次排查自动配置问题我都是先看这段报告里的Negative matches基本能定位九成问题。还有一招如果你的自动配置类连报告里都没出现说明它压根没被作为候选加载。这时候去检查imports文件里的类名是不是拼错了。这个错误没有任何日志会提示纯粹是“写上就开摆”只能靠细心。有个土办法把类名复制过去不要手打。别觉得这是废话我见过太多人就是栽在大小写和包名上。4.3 自动配置之间的顺序控制before和after怎么用最后一个实操价值很高的点是自动配置的排序。当一个项目里有多个starter时自动配置类之间的执行顺序很关键。比如你的配置类需要先于DataSourceAutoConfiguration执行或者必须晚于某个缓存配置类执行。排序有两种写法。第一种直接在AutoConfiguration注解上声明AutoConfiguration( before org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, after com.example.cache.CacheAutoConfiguration.class )第二种沿用老的注解AutoConfigureBefore(DataSourceAutoConfiguration.class) AutoConfigureAfter(CacheAutoConfiguration.class)两者是等价的本质上Spring Boot在收集完所有自动配置候选后会按照这些声明做一次拓扑排序。这里容易踩的一个坑是跨jar包的类名引用。before DataSourceAutoConfiguration.class这种写法会导致编译期必须能解析到那个类如果那个类来自一个scope为optional的依赖编译会直接失败。所以跨模块、跨starter排序时我建议优先用全限定名字符串的方式AutoConfiguration(beforeName org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration)这种写法把编译期依赖解耦了只要运行期类存在就行可维护性也好很多。还有个小经验排序不要滥用。很多初学者喜欢给每个自动配置类都加上一堆before/after结果把类之间的耦合搞得很重。合理的做法是只在确实存在依赖约束时才声明能用ConditionalOnClass兜底的就别用排序硬控。最后再分享一个小技巧。迁移完成之后不用急着删老文件可以先在Spring Boot 2.7版本下跑通整套逻辑确认新文件生效了再考虑升3.x。我当时就是这么做的先在2.7下同时保留两个文件让自动配置统一走新路径观察几轮启动日志没异常再升级到3.x。升完之后记得做一次启动耗时对比。新文件少了Properties解析那层启动时间会略有一点下降虽然幅度不大但至少能证明新机制真的在替你干活。整个过程如果你也打算自己操作建议先把jar tf检查养成习惯每次改完自动配置记得看一眼jar包里的文件能省下后面一堆排查时间。
返回列表