Spring.factories深入解析:自动配置原理与Starter实战 1. 先搞清楚Spring.factories是干什么的前阵子帮一个同事排查问题他的项目只引入了一个内部基础组件的依赖什么都没配但启动后RedisTemplate、KafkaTemplate全都自动存在了。他翻遍代码也没找到任何一个Bean跑来问我这 Bean 到底是谁帮我 new 出来的 我让他去依赖的 jar 包里找了一个文件META-INF/spring.factories。看完他就明白了。如果你也写过 Spring Boot 应用并且对引入一个 starter 依赖就自动拥有一堆 Bean这个现象产生过好奇那我建议把这篇文章看完。不管你是做业务开发的还是要写中间件 SDK、公司内部公共组件、第三方 Starter这个东西早晚会碰到。理解了Spring.factories你就理解了 Spring Boot 自动配置体系的入口在哪儿以后遇到自动配置没生效Bean 被重复创建这类问题也知道该从哪里下手。1.1 一个简单的疑问为什么引入依赖就自动注入 Bean传统 Spring 项目里要往容器里放一个 Bean你得写 XML或者写一堆ConfigurationBean。如果依赖了第三方库通常得手动把它需要的 Bean 也声明好非常繁琐。Spring Boot 把这个过程自动化了它约定凡是需要在应用启动时被自动装配的配置类都会以某种统一的方式登记起来。Boot 启动时在创建容器之前会先把/CLASSPATH下所有 jar 包里的这些登记信息扫描一遍把候选的配置类找出来。这些配置类内部通常写满了条件判断注解比如当前 classpath 里有这个类才装配当前配置项没被用户改过才装配等等。条件满足才会真正创建对应的 Bean。而spring.factories就是这份登记表最核心的载体之一。它里面保存了各种扩展点的实现类名录Spring Boot 通过它获取到哪些类需要被加载、监听哪些事件、初始化哪些逻辑。你只需要引入一个依赖jar 包里的spring.factories就被扫描到自动配置随即生效。自动配置这三个字听起来很玄落到文件层面其实就是这么一回事。1.2 Spring.factories的本质比Java SPI更好用的扩展点注册表spring.factories并不是 Spring 独创的概念它的核心思想是大家常说的 SPIService Provider Interface。Java 本身也提供了一套 SPI 机制即ServiceLoader要求在META-INF/services目录下创建一个以接口全限定名为文件名、内容为实现类全限定名的资源文件。用起来其实还行但有一个问题如果扩展点很多你得建很多个小文件管理起来比较零散。Spring 自己实现了一套类似的机制叫SpringFactoriesLoader。它用一个总文件META-INF/spring.factories把几乎所有扩展点集中登记在一起。文件的本质和 JavaProperties文件一样一个 key 对应一个或多个 value多个 value 之间用逗号分隔。举个例子下面是某个自动配置模块里常见的写法org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.common.autoconfigure.CommonAutoConfiguration,\ com.example.common.autoconfigure.MetricsAutoConfiguration这里等号左边是接口或注解的全限定名等号右边是这个扩展点的具体实现类多个类按逗号分隔。如果一行太长可以用反斜杠续行。加载逻辑也很简单SpringFactoriesLoader会扫描 classpath 下所有META-INF/spring.factories资源把它们解析成 Properties然后根据不同的 key 取出对应的类名列表再用ClassLoader加载并实例化。整个过程比ServiceLoader直观得多因为你打开一个文件就能看到这个 jar 包对外暴露了哪些扩展点。所以我把spring.factories理解成项目的服务登记表接口和实现类的关系不再散落在代码里而是统一记录在resources/META-INF/spring.factories下。Spring Boot 启动时它就是靠这份登记表知道该加载哪些自动配置类的。2. Spring.factories里的五大注册位分别对应什么能力很多人一提到spring.factories脑子里立刻想到自动配置。这没错但它能干的远不止自动配置。随着 Spring Boot 版本演进虽然自动配置这一项在 2.7 之后被移到了新文件里但spring.factories本身依然承载着多个扩展点。2.1 EnableAutoConfiguration自动配置的总入口只要你是从spring.factories时代过来的最常接触的 key 一定是这个org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.XxxAutoConfigurationSpring Boot 启动过程中AutoConfigurationImportSelector会读取这个 key 下的所有配置类然后进行过滤、排序最终把它们作为自动配置候选类交给容器。这个 key 对应的实现类有几个约定值得留意一般是Configuration配置类推荐使用Configuration(proxyBeanMethods false)减少不必要的 CGLIB 代理开销。类名通常以AutoConfiguration结尾这是社区习惯方便一眼识别。配置类内部要有条件判断典型的就是ConditionalOnClass、ConditionalOnProperty、ConditionalOnMissingBean这一组注解。一个必须避开的坑是自动配置类不要放在业务启动类的包扫描范围里。如果被ComponentScan扫到了它在常规扫描阶段就会被注册成一个普通配置类自动配置筛选阶段的ConditionalOnMissingBean等判定可能就会失效进而引发意料之外的重复装配。我见过不止一次有人把自动配置类和Application启动类放在同一个包下结果Bean被创建了两遍。2.2 另外四个容易被忽略的扩展点spring.factories里除了自动配置还有几个注册位业务开发可能一辈子都用不上但写组件的人很可能会用到。key 配置项接口类型典型用途org.springframework.context.ApplicationContextInitializerApplicationContextInitializer在 IoC 容器刷新前对ConfigurableApplicationContext做定制例如提前注册一些属性源、自定义 environmentorg.springframework.context.ApplicationListenerApplicationListener监听 Spring 声明周期事件例如ApplicationStartedEvent、ContextClosedEvent适合做启动后回调或优雅停机钩子org.springframework.boot.env.EnvironmentPostProcessorEnvironmentPostProcessor在 Environment 准备完成后修改配置常用于把公司私有配置中心的数据塞进运行环境里org.springframework.boot.SpringApplicationRunListenerSpringApplicationRunListener监听SpringApplication的启动全流程从starting到ready的每个阶段都能介入以EnvironmentPostProcessor为例。我之前在公司做配置中心接入时就是用它实现的应用启动阶段SpringApplication.run会先创建 Environment随后立刻回调所有EnvironmentPostProcessor我们在里面远程拉取配置中心的 key-value然后通过propertySource追加到 Environment 中。这样业务代码里Value和ConfigurationProperties就能直接读到远端配置整个过程对使用方完全透明。这四个扩展点的使用频率虽然远低于自动配置但它们的价值在于这是不修改 Spring Boot 源码、不依赖业务代码就能注入自定义逻辑的官方预留通道。理解了spring.factories里每个 key 的含义再看一些中间件源码时会轻松很多。3. 从零手写一个StarterSpring.factories的完整实战讲完机制我带你手写一个完整的 Starter。这个例子的目标很简单引入依赖后只要在application.yml里打开开关容器中就自动注入一个GreetingService。3.1 工程结构starter 模块和 autoconfigure 模块拆开Spring 官方 Starter 惯用的做法是拆成两个 Maven 模块一个 starter 模块只做依赖聚合不写任何业务代码另一个 autoconfigure 模块放真正的逻辑和spring.factories。这样设计的好处是依赖方只需要引一个 starter 坐标而内部实现可以被独立维护也方便做编译期依赖隔离。目录结构大致是这样hello-spring-boot-starter/ ├─ pom.xml ├─ hello-spring-boot-starter/ │ └─ pom.xml # 只依赖 hello-spring-boot-autoconfigure └─ hello-spring-boot-autoconfigure/ ├─ pom.xml └─ src/main/ ├─ java/com/example/hello/ │ ├─ GreetingService.java │ ├─ GreetingProperties.java │ └─ GreetingAutoConfiguration.java └─ resources/META-INF/ └─ spring.factoriesstarter 模块的 pom 核心就一行依赖dependency groupIdcom.example/groupId artifactIdhello-spring-boot-autoconfigure/artifactId version0.0.1-SNAPSHOT/version /dependency如果你在 autoconfigure 模块中使用了条件化配置且希望 IDE 提示更友好还可以加上官方注解处理器dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-autoconfigure-processor/artifactId optionaltrue/optional /dependency这个处理器会在编译期生成META-INF/spring-autoconfigure-metadata.properties保存自动配置类和条件注解的关系让 Spring Boot 在启动时能提前过滤一部分明显不满足条件的类减少类加载开销属于性能优化层面的加分项。3.2 自动配置类的写法条件装配如何搭配先写配置属性类它负责接收application.yml里的自定义配置ConfigurationProperties(prefix hello.greeting) public class GreetingProperties { private boolean enabled true; private String words Hello, Spring Boot!; public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled enabled; } public String getWords() { return words; } public void setWords(String words) { this.words words; } }再写服务类不复杂就是一个普通的 POJOpublic class GreetingService { private final String words; public GreetingService(String words) { this.words words; } public void greet() { System.out.println(words); } }然后是核心的自动配置类Configuration(proxyBeanMethods false) EnableConfigurationProperties(GreetingProperties.class) ConditionalOnProperty(prefix hello.greeting, name enabled, havingValue true, matchIfMissing true) public class GreetingAutoConfiguration { Bean ConditionalOnMissingBean public GreetingService greetingService(GreetingProperties properties) { return new GreetingService(properties.getWords()); } }这里有几个细节值得展开说一下。ConditionalOnProperty表示只有当配置项hello.greeting.enabledtrue的时候才装配matchIfMissing true表示即使配置文件里没写这个配置项也算匹配成功。这样一来使用者想启用时就写hello.greeting.enabled: true想关闭时就写false非常灵活。ConditionalOnMissingBean是给使用方留的后门如果用户在业务代码里已经自己定义了一个GreetingServiceBean那么自动配置的这个就不会再去创建。这个注解在写通用组件时几乎必备能有效避免团队内部你配一个、框架再配一个的冲突。Configuration(proxyBeanMethods false)是为了省掉配置类的 CGLIB 代理。如果你确定配置类内部的Bean方法之间不需要互相调用建议都写成false能减少容器启动时的代理开销这也是 Spring Boot 自动配置类里的标准写法。3.3 spring.factories文件长什么样写在哪自动配置类写完剩下的关键一步是把这个类登记到spring.factories里。文件路径必须是src/main/resources/META-INF/spring.factories内容如下org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.hello.GreetingAutoConfiguration如果你的模块里有两个自动配置类就继续用逗号分隔org.springframework.boot.autoconfigure.EnableAutoConfiguration\ com.example.hello.GreetingAutoConfiguration,\ com.example.hello.GreetingSecurityAutoConfiguration注意这个文件本质是 Java Properties 格式默认编码是 ISO-8859-1所以最好不要在文件里写中文注释如果非要写需要转成\uXXXX形式的 Unicode 转义否则容易出现乱码导致配置解析异常。写成全英文注释是最省心的。这一步完成之后打包发布。使用方只要在pom.xml里引入 starter 依赖然后在application.yml里这样配置hello: greeting: enabled: true words: 你好自动配置应用启动后GreetingService就自动出现在容器里了业务代码可以直接注入使用。这就是一个最基础的自动配置闭环。4. 配置不生效、重复加载、顺序错乱这些坑我全踩过spring.factories本身并不复杂但正因为加载过程太透明出了问题反而很难察觉。下面几个坑都是我在实际开发中踩过或者帮别人排查过的按发生频率从高到低列出来。4.1 文件路径和文件名错了启动时却不报错这是最常见也最隐蔽的问题。SpringFactoriesLoader扫描资源时如果找不到META-INF/spring.factories它直接返回空集合不会抛异常也不会在日志里说有文件缺失。于是表现为依赖引了、代码看起来没问题但自动配置就是不生效。我自己就犯过一次把META-INF打成了META-INFO。对就差一个字母启动完全正常只是组件没有工作。排查时翻了半天源码最后打开打出来的 jar 包才发现资源目录名错了。所以排查这类问题时第一件事就是进入构建产物里确认文件真的存在。如果是 Spring Boot 可执行 jar路径在BOOT-INF/classes/META-INF/spring.factories另外文件名也有讲究。不要写成spring.factory、springs.factories这类变体Spring 只认spring.factories。如果用的是新版AutoConfiguration.imports文件路径则长得多更容易抄错建议直接复制官方路径META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。4.2 自动配置类被加载但Bean没生效如果文件路径和内容都没问题但 Bean 还是没有注入那大概率是条件装配没有匹配上。这时候最有效的工具是启动时打印自动配置报告。有两种方式运行 jar 时加--debug参数在application.yml里设置debug: true。启动后日志会输出一份CONDITIONS EVALUATION REPORT里面记录了每个自动配置类的匹配结果。看到Negative matches段就能知道是哪个条件没满足。常见的几种原因ConditionalOnClass指定的类在当前 classpath 里不存在比如依赖没有真正被引入。ConditionalOnProperty的prefix、name、havingValue写错了配置值和预期不匹配。ConditionalOnMissingBean生效但容器里恰好已有同名 Bean导致当前装配被跳过。拿上面的例子来说如果我把ConditionalOnProperty的havingValue写成了TRUE而用户配置写的是true条件就不满足。这类问题不看到报告很难定位建议写容器组件时把这份报告作为排查问题的第一站。4.3 如何快速确认spring.factories有没有被读到有些问题是条件判断解决不了的你需要确认启动阶段到底有没有读到这个文件。我常用的方法有两个。一个是在SpringFactoriesLoader的相关方法上打断点。旧版本可以直接断在loadFactoryNames观察返回的类名集合里有没有你写的配置类。新版本 Spring Boot 3 读取自动配置类时走的已经不是这个方法而是通过AutoConfigurationImportSelector从 imports 文件读取可以断在候选配置类加载后的方法上直接看集合内容。另一个办法是引入spring-boot-starter-actuator查看conditions端点curl http://localhost:8080/actuator/conditions它会返回当前应用所有自动配置类的正负匹配详情。和--debug报告差不多但不用看日志更适合线上接口式诊断。另外如果你想临时关掉某个自动配置类可以在启动类上排除或者在配置文件里指定spring.autoconfigure.excludecom.example.hello.GreetingAutoConfiguration这个排除机制本身也是排查好帮手如果排除了某个类之后问题依旧存在说明问题根本不在这个自动配置类上。4.4 多个自动配置类的顺序问题当spring.factories里登记了多个自动配置类时加载顺序并不是简单地按文件里的声明先后来的。Spring Boot 会自动对所有自动配置类排序排序规则有三个注解AutoConfigureOrder设置全局顺序值越小优先级越高。AutoConfigureBefore声明该配置类要在哪个配置类之前生效。AutoConfigureAfter声明该配置类要在哪个配置类之后生效。如果你的配置类会依赖另一个组件创建的 Bean务必要用AutoConfigureBefore或AutoConfigureAfter把顺序关系写清楚。例如Configuration(proxyBeanMethods false) AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MyRepositoryAutoConfiguration { // 这里假设依赖 DataSource 已经装配完成 }这个坑在排序上非常隐蔽。它不会像路径写错那样不生效而是可能让你的 Bean 在别人还缺依赖的时候被抢先创建然后启动报错错误信息和你的配置类又对不上排查起来很绕。5. Spring Boot 2.7以后官方为什么弃用spring.factories的自动配置位如果你最近创建新的 Spring Boot 项目打开自动配置模块的 jar 包可能发现spring.factories里已经找不到EnableAutoConfiguration这个 key 了取而代之的是一个更短的文本文件。5.1 AutoConfiguration.imports的来历和写法Spring Boot 2.7 发布时官方引入了新文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件的格式极其简单每行写一个自动配置类的全限定名不用管 key、等号、逗号和反斜杠。比如上面例子里的配置类在新文件里就是一行com.example.hello.GreetingAutoConfiguration到了 Spring Boot 3.0spring.factories里的org.springframework.boot.autoconfigure.EnableAutoConfiguration这个 key 就不再被支持了。如果你把项目升级到 Boot 3.x还守着旧的spring.factories写法自动配置会直接失效。为什么官方要做这个替换我的理解有三个原因。第一spring.factories承担了太多职责自动配置、监听器、环境后处理器全混在一个 Properties 文件里key 很长、可读性一般ide 也没法给出代码提示。第二Properties 格式用逗号和反斜杠续行在多团队成员维护时很容易格式化出错看起来乱糟糟的。第三独立的 imports 文件让自动配置类的列表一目了然构建工具可以更方便地检查、分析它。有一点要特别说明spring.factories文件本身并没有被废弃它仍然负责ApplicationContextInitializer、ApplicationListener、EnvironmentPostProcessor等扩展点的注册。只是自动配置类的登记这一项职责被划到了新文件里。5.2 存量项目怎么平滑迁移如果你维护的组件目前还在用spring.factories注册自动配置类建议不要一步到位而是走一个过渡期。第一步把META-INF/spring.factories里的自动配置类声明摘出来照抄到新路径的AutoConfiguration.imports文件中。一个类一行不要用逗号分隔。第二步判断你的组件需要兼容的最低 Spring Boot 版本。如果还需要支持 Boot 2.6 及以下的老项目那只能暂时保留spring.factories里的EnableAutoConfiguration条目。这里最需要注意的是同一个自动配置类在过渡期不要同时声明在旧文件和新文件里。官方虽然会合并两个来源的候选类但两边都写同一个类存在重复解析的边界风险没必要去赌这个行为。最干净的做法是旧版本项目用旧文件新版本项目用新文件通过 Maven 的 profile 或依赖版本区分。第三步确认 Spring Boot 版本后记得用--debug启动一次应用检查自动配置报告确保迁移后配置类依然被正确识别没有被重复加载或排除。我的建议是如果你还在维护老组件可以给一个明确的版本号边界例如1.x继续支持spring.factories2.x率先切换到AutoConfiguration.imports并要求使用者升级到 Spring Boot 2.7 以上。拖得越晚将来升级成本越高。最后再分享一个小技巧新版的AutoConfiguration.imports文件在 IDEA 里会有原生支持行高亮、右键跳转类都很方便而旧版spring.factories在 IDE 里就是纯文本跳转实现类要靠手写全限定名后再手动查找。如果你要频繁维护自动配置类早一天切换到新文件早一天舒服。