Spring Boot Starter 不再神秘:从 spring.factories 的消亡看云原生野心
一行依赖的“魔法”?我们自己也能造
我们都享受过 Spring Boot Starter 带来的便利:想用 Web?spring-boot-starter-web。想用 Redis?spring-boot-starter-data-redis。一行依赖,整个世界都为你配置好了。这种“约定优于配置”的丝滑体验,其实我们自己也能实现。
最近团队要全面接入 XXL-Job,与其让每个人都去复制那一大坨 XxlJobSpringExecutor 的配置,不如我直接封装一个 Starter,一劳永逸。
一个合格的 Starter,本质上只由三部分组成:
- Properties 类:一个 POJO,用来接收
application.yml里的配置。 - Configuration 类:核心,根据配置决定是否以及如何创建 Bean。
- 注册文件:一个声明文件,告诉 Spring Boot:“嘿,我这里有个自动配置,快来加载我。”

1. @ConfigurationProperties:让配置注入变得优雅
首先,我们需要一个类来承载配置。别再用 @Value 一个个去读了,@ConfigurationProperties 可以直接把 application.yml 中的一个区块映射成一个对象。
@ConfigurationProperties比@Value更适合结构化配置,支持 JSR-303 校验,还能在 IDE 里获得完美的提示。- 提供一个
enabled开关是最佳实践,它让使用者可以轻松地在不同环境中禁用你的 Starter,而无需删除依赖。
import org.springframework.boot.context.properties.ConfigurationProperties;
// 将 application.yml 中以 "spring.xxl.job" 开头的配置绑定到这个类的字段上
@ConfigurationProperties(prefix = "spring.xxl.job")
public class XxlJobProperties {
// 控制此 starter 是否生效,这是企业级 starter 的标配
private boolean enabled;
private String adminAddresses;
private String accessToken;
private String appName;
private String ip;
private int port;
private String logPath;
private int logRetentionDays = 30; // 提供一个合理的默认值
// 省略 a-lot-of Getters and Setters...
}
2. @Configuration 与 @Conditional:Starter 的大脑
接下来是核心的自动配置类。它的任务是读取 XxlJobProperties,然后决定是否要创建一个 XxlJobSpringExecutor 的 Bean。优势如下:
- 可维护性:
@ConditionalOnProperty是黄金搭档,它确保了只有在用户显式开启时,你的自动配置才会介入,避免意外的 Bean 创建。 - 可靠性/扩展性:
@ConditionalOnMissingBean给了用户“逃生舱”。如果你的默认配置不满足他们的特殊需求,他们可以自己@Bean一个同类型的实例,Spring Boot 会优先使用用户的,你的自动配置则会安静地退下。 - 可观测性: 在关键配置初始化时打印一条日志,是排查问题的救命稻草。当用户抱怨“配置不生效”时,你可以让他先去日志里搜一下这条 info log。
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
// 激活我们刚才定义的 Properties 类,让它能够被注入
@EnableConfigurationProperties(XxlJobProperties.class)
// 只有当 spring.xxl.job.enabled=true 时,这个 Configuration 才会生效
@ConditionalOnProperty(prefix = "spring.xxl.job", name = "enabled", havingValue = "true")
public class XxlJobConfiguration {
private static final Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class);
@Autowired
private XxlJobProperties properties;
@Bean
// 允许用户自己定义一个 XxlJobSpringExecutor 来覆盖我们的默认实现,提供灵活性
@ConditionalOnMissingBean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>> xxl-job config init, adminAddresses:{}", properties.getAdminAddresses());
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(properties.getAdminAddresses());
xxlJobSpringExecutor.setAppname(properties.getAppName());
xxlJobSpringExecutor.setIp(properties.getIp());
xxlJobSpringExecutor.setPort(properties.getPort());
xxlJobSpringExecutor.setAccessToken(properties.getAccessToken());
xxlJobSpringExecutor.setLogPath(properties.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(properties.getLogRetentionDays());
return xxlJobSpringExecutor;
}
}
再见 spring.factories,你好云原生
现在,我们有了配置类和属性类,最后一步就是告诉 Spring Boot 去哪里找到它们。

spring.factories 的时代
在 Spring Boot 2.7 之前,我们会在 src/main/resources/META-INF/ 目录下创建一个 spring.factories 文件,内容如下:
# DEPRECATED since Spring Boot 2.7
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.your.company.XxlJobConfiguration
这个文件就像一个“藏宝图”,Spring Boot 启动时会扫描所有 Jar 包里的这个文件,把 EnableAutoConfiguration key 下的所有类都加载进来。简单直接,但也问题重重。
更专注、更高效的 .imports 文件
从 Spring Boot 2.7 开始,并在 3.0 中成为唯一标准的方式是:在 src/main/resources/META-INF/spring/ 目录下创建一个名为 org.springframework.boot.autoconfigure.AutoConfiguration.imports 的文件。
注意这个路径,它比以前更深了。文件内容也更纯粹:
// No key, just the fully qualified class name
com.your.company.XxlJobConfiguration
就是这么简单,一行一个配置类的全限定名。
为什么要做这个改变?答案在编译期
Spring 团队当然不是闲得无聊才做这种破坏性变更。废弃 spring.factories 的核心驱动力是性能,以及对云原生和 GraalVM Native Image 的全面拥抱。
-
spring.factories 的问题:它是一个运行时机制。Spring Boot 启动时,必须遍历 Classpath 下所有 Jar 包,打开每一个
spring.factories文件,解析这个 properties 文件,然后加载里面的类。这个过程涉及大量的 I/O 操作和字符串处理,在应用启动时是一笔不小的开销。对于需要 AOT(Ahead-Of-Time)编译的 GraalVM 来说,这种动态扫描机制更是噩梦,它很难在编译期就精确地知道哪些自动配置是必须的。 -
.imports 的优势:这是一个编译期友好(AOT-friendly)的机制。新的
.imports文件是一个纯文本列表,格式极其简单。Spring Boot 的构建插件可以在编译时就扫描到这些文件,生成一个静态的自动配置类索引。当应用启动时,它不再需要去满世界地扫描和解析,而是直接加载这个索引好的列表。
这个看似微小的改动,将自动配置的发现过程从“运行时动态搜索”变成了“编译期静态链接”,大大缩短了启动时间,降低了内存占用,并为 GraalVM 将 Spring 应用编译成一个超快启动的本地可执行文件铺平了道路。
要点速记
- 封装通用能力,别让团队成员重复“发明轮子”。
- 用
@ConfigurationProperties提供类型安全的配置绑定。 enabled开关和@ConditionalOnProperty是必备的保险丝。- 用
@ConditionalOnMissingBean保证你的 Starter 不“霸道”,给用户留后路。 - 放弃
spring.factories,立刻转向.imports文件,这是面向未来的选择。 - 别忘了在
pom.xml中把非必要的依赖 scope 设置为optional,避免依赖传递污染。
一个优秀的 Starter,不仅是代码的封装,更是最佳实践的沉淀
- 明确的属性前缀:为你的 starter 选择一个独特的、不易冲突的前缀,如
spring.company.feature。 - 默认开关关闭:默认将
enabled设置为false通常更安全,强迫用户显式开启。 - 提供覆盖机制:除了
@ConditionalOnMissingBean,还可以考虑@ConditionalOnClass和@ConditionalOnWebApplication等来应对更复杂的场景。 - 依赖管理要干净:在 starter 的
pom.xml中,将所有非必需的依赖标记为<optional>true</optional>,让用户项目自己决定是否引入。 - 文档先行:为你的 starter 编写清晰的 README,说明每个配置项的作用、默认值和用法。
- 日志是你的朋友:在配置加载的关键节点,打印有意义的日志。
- 保持向后兼容:如果需要修改配置项的名称,要考虑老版本的兼容性,可以使用
@ConfigurationProperties的废弃属性支持。 - 拥抱新标准:立即使用
.imports文件,这不仅是技术趋势,也是性能优化的关键一步。
好的工具,应该像空气一样,你需要时它就在,你感觉不到时,它依然在可靠地工作。
更多推荐



所有评论(0)