一行依赖的“魔法”?我们自己也能造

我们都享受过 Spring Boot Starter 带来的便利:想用 Web?spring-boot-starter-web。想用 Redis?spring-boot-starter-data-redis。一行依赖,整个世界都为你配置好了。这种“约定优于配置”的丝滑体验,其实我们自己也能实现。

最近团队要全面接入 XXL-Job,与其让每个人都去复制那一大坨 XxlJobSpringExecutor 的配置,不如我直接封装一个 Starter,一劳永逸。

一个合格的 Starter,本质上只由三部分组成:

  1. Properties 类:一个 POJO,用来接收 application.yml 里的配置。
  2. Configuration 类:核心,根据配置决定是否以及如何创建 Bean。
  3. 注册文件:一个声明文件,告诉 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 应用编译成一个超快启动的本地可执行文件铺平了道路。

要点速记

  1. 封装通用能力,别让团队成员重复“发明轮子”。
  2. 用 @ConfigurationProperties 提供类型安全的配置绑定。
  3. enabled 开关和 @ConditionalOnProperty 是必备的保险丝。
  4. 用 @ConditionalOnMissingBean 保证你的 Starter 不“霸道”,给用户留后路。
  5. 放弃 spring.factories,立刻转向 .imports 文件,这是面向未来的选择。
  6. 别忘了在 pom.xml 中把非必要的依赖 scope 设置为 optional,避免依赖传递污染。

一个优秀的 Starter,不仅是代码的封装,更是最佳实践的沉淀

  • 明确的属性前缀:为你的 starter 选择一个独特的、不易冲突的前缀,如 spring.company.feature。
  • 默认开关关闭:默认将 enabled 设置为 false 通常更安全,强迫用户显式开启。
  • 提供覆盖机制:除了 @ConditionalOnMissingBean,还可以考虑 @ConditionalOnClass 和 @ConditionalOnWebApplication 等来应对更复杂的场景。
  • 依赖管理要干净:在 starter 的 pom.xml 中,将所有非必需的依赖标记为 <optional>true</optional>,让用户项目自己决定是否引入。
  • 文档先行:为你的 starter 编写清晰的 README,说明每个配置项的作用、默认值和用法。
  • 日志是你的朋友:在配置加载的关键节点,打印有意义的日志。
  • 保持向后兼容:如果需要修改配置项的名称,要考虑老版本的兼容性,可以使用 @ConfigurationProperties 的废弃属性支持。
  • 拥抱新标准:立即使用 .imports 文件,这不仅是技术趋势,也是性能优化的关键一步。

好的工具,应该像空气一样,你需要时它就在,你感觉不到时,它依然在可靠地工作。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐