打造专属Spring Boot Starter

如何为自己的库开发一个 Spring Boot Starter,并实现自动配置(Auto-Configuration)功能。

这属于 Spring Boot 高级开发技巧,通常用于:

  • 开发公司内部共享组件
  • 创建开源项目或商业 SDK
  • 封装通用能力(如短信、支付、日志增强等)

我将用 通俗语言 + 结构化方式 帮你彻底理解这一节的核心思想和实践步骤。


🌟 一、核心目标:什么是"自定义 Starter"?

✅ 一句话解释:

你想让你的 Java 库(比如叫 acme-sdk)能像 Spring Boot 的官方模块一样,只要引入一个依赖,就能自动生效、开箱即用

例如:

xml 复制代码
<dependency>
    <groupId>com.acme</groupId>
    <artifactId>acme-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

引入后:

  • 自动配置好 AcmeClient
  • 支持 application.yml 中通过 acme.xxx 配置参数
  • 如果类路径有某个类才启用 → 智能判断

这就是所谓的 "Starter + Auto-Configuration" 机制


🔧 二、整体结构:一个典型的 Starter 包含什么?

一个完整的 Spring Boot Starter 通常由两个模块组成:

模块 作用
acme-spring-boot-autoconfigure 核心:包含自动配置逻辑、条件判断、@ConfigurationProperties
acme-spring-boot-starter 外壳:只负责引入 autoconfigure 模块 + 实际 SDK 依赖

示例目录结构:

复制代码
acme-spring-boot-starter/
├── pom.xml                          # 只引入 acme-autoconfigure 和 acme-sdk
└── src/
    └── main/
        └── resources/
            └── META-INF/
                └── maven/...

acme-spring-boot-autoconfigure/
├── pom.xml                          # 依赖 spring-boot-autoconfigure, acme-sdk (optional)
├── src/
│   └── main/
│       └── java/
│           └── com/acme/autoconfigure/
│               ├── AcmeAutoConfiguration.java
│               └── AcmeProperties.java
│       └── resources/
│           └── META-INF/
│               ├── spring.factories
│               └── spring-configuration-metadata.json

📌 注意:这两个模块可以合并成一个,但如果功能复杂、可选特性多,建议拆开。


🛠️ 三、关键步骤详解

Step 1:写一个 Auto-Configuration 类

这是最核心的部分 ------ 写一个带有 @Configuration 的类,告诉 Spring Boot "在什么条件下创建哪些 Bean"。

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AcmeClient.class)                    // 只有 AcmeClient 类存在时才加载
@ConditionalOnMissingBean                             // 用户没自己定义 AcmeClient 才创建
@EnableConfigurationProperties(AcmeProperties.class)   // 启用配置绑定
public class AcmeAutoConfiguration {

    @Bean
    public AcmeClient acmeClient(AcmeProperties properties) {
        return new AcmeClient(properties.getHost(), properties.getPort());
    }
}

✅ 这个类会在满足条件时自动注册 AcmeClient 到 Spring 容器。


Step 2:定义配置属性(支持 application.yml)

为了让用户可以通过 application.yml 配置你的组件,你需要定义 @ConfigurationProperties

java 复制代码
@ConfigurationProperties("acme")
public class AcmeProperties {

    /**
     * Whether to check the location of acme resources.
     */
    private boolean checkLocation = true;

    /**
     * Timeout for establishing a connection to the acme server.
     */
    private Duration loginTimeout = Duration.ofSeconds(3);

    // getters & setters...
}

这样用户就可以写:

yaml 复制代码
acme:
  host: api.acme.com
  port: 8080
  login-timeout: 5s
  check-location: true

💡 IDE 还会提示补全(因为生成了元数据文件)!


Step 3:注册 Auto-Configuration(关键!)

Spring Boot 不会自动发现你的配置类,必须通过 META-INF/spring.factories 文件显式声明。

文件路径:
复制代码
src/main/resources/META-INF/spring.factories
内容:
properties 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.autoconfigure.AcmeAutoConfiguration,\
com.acme.autoconfigure.AcmeWebAutoConfiguration

⚠️ 注意事项:

  • 必须是这个 key:EnableAutoConfiguration
  • 多个类用 \ 换行连接
  • 该类不能被 @ComponentScan 扫到(避免重复加载)

Step 4:添加条件注解(Condition Annotations)

为了让自动配置更智能,你可以使用各种 @ConditionalOnXXX 来控制是否生效。

注解 用途
@ConditionalOnClass(Xxx.class) 当类路径中有 Xxx 类才启用
@ConditionalOnMissingBean 当容器中没有该类型 Bean 才创建
@ConditionalOnProperty(name="acme.enabled", havingValue="true") 当配置项开启时才启用
@ConditionalOnResource("classpath:acme-config.xml") 存在某个资源文件才启用
@ConditionalOnWebApplication 只在 Web 应用中启用
@ConditionalOnExpression("${flag} == true") SpEL 表达式控制

🎯 组合使用这些注解,可以让你的 starter 更加健壮、灵活。


Step 5:优化启动性能(推荐)

Spring Boot 提供了一个注解处理器,可以在编译期收集所有条件信息,加快启动速度。

添加依赖(在 autoconfigure 模块中):
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>

它会在编译后生成 META-INF/spring-autoconfigure-metadata.properties,帮助 Spring Boot 提前过滤不匹配的配置类。

📌 注意:如果你只是在应用内写配置类(不是打包发布),不要把这个 jar 打进 fat jar。

Maven 排除方式:

xml 复制代码
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <excludes>
            <exclude>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure-processor</artifactId>
            </exclude>
        </excludes>
    </configuration>
</plugin>

Step 6:命名规范(重要!)

❌ 错误命名:
  • spring-boot-starter-acme (官方保留)
  • mycompany-acme-starter (缺少命名空间)
✅ 正确命名:
  • acme-spring-boot-starter
  • acme-spring-boot-autoconfigure

规则总结:

  1. 不要以 spring-boot 开头
  2. 使用你自己的命名空间(如 acme
  3. starter 模块命名为 xxx-spring-boot-starter
  4. autoconfigure 模块命名为 xxx-spring-boot-autoconfigure

Step 7:编写测试(确保稳定性)

Spring Boot 提供了 ApplicationContextRunner 工具来测试自动配置行为。

示例测试代码:
java 复制代码
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(AcmeAutoConfiguration.class));

@Test
void defaultServiceIsCreated() {
    contextRunner.run(context -> {
        assertThat(context).hasSingleBean(AcmeClient.class);
    });
}

@Test
void customUserServiceOverridesAutoConfig() {
    contextRunner.withUserConfiguration(UserConfig.class).run(context -> {
        assertThat(context).getBean("myAcmeClient").isSameAs(context.getBean(AcmeClient.class));
    });
}

@Test
void whenPropertySet_thenUsesCustomHost() {
    contextRunner.withPropertyValues("acme.host=testhost").run(context -> {
        AcmeClient client = context.getBean(AcmeClient.class);
        assertThat(client.getHost()).isEqualTo("testhost");
    });
}

@Test
void whenAcmeClientNotPresent_thenAutoConfigDisabled() {
    contextRunner.withClassLoader(new FilteredClassLoader(AcmeClient.class))
                 .run(context -> {
                     assertThat(context).doesNotHaveBean(AcmeClient.class);
                 });
}

// 测试类
static class UserConfig {
    @Bean
    AcmeClient myAcmeClient() {
        return new AcmeClient("custom-host", 9999);
    }
}

✅ 这些测试可以验证:

  • 自动配置是否正常工作
  • 用户自定义 Bean 是否能覆盖
  • 配置项是否生效
  • 缺少依赖时是否会自动禁用

🧪 四、高级技巧补充

1. 控制加载顺序

有时候你的配置需要在其他配置之后执行(比如依赖 DataSource)。

java 复制代码
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class AcmeJdbcAutoConfiguration { ... }

或者指定优先级:

java 复制代码
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)

2. 支持 Reactive 或 Servlet 环境

java 复制代码
@ConditionalOnWebApplication(type = Type.SERVLET)
public class AcmeServletAutoConfiguration { }

@ConditionalOnWebApplication(type = Type.REACTIVE)
public class AcmeReactiveAutoConfiguration { }

3. 文档化配置项(IDE 友好)

确保每个字段都有 Javadoc,这样会自动生成 spring-configuration-metadata.json,让 IDE 显示提示。

java 复制代码
/**
 * Comma-separated list of locations to scan for acme resources.
 */
private List<String> locations = Arrays.asList("/default");

/**
 * Enable debug mode for acme engine.
 */
private boolean debug = false;

✅ 总结:开发自定义 Starter 的完整流程

步骤 操作
1️⃣ 创建两个模块:xxx-spring-boot-autoconfigurexxx-spring-boot-starter
2️⃣ 在 autoconfigure 模块中编写 @Configuration 类,使用 @ConditionalOnXXX 控制条件
3️⃣ 定义 @ConfigurationProperties("xxx") 绑定配置项
4️⃣ META-INF/spring.factories 中注册自动配置类
5️⃣ 添加 spring-boot-autoconfigure-processor 提升启动性能
6️⃣ 编写单元测试,使用 ApplicationContextRunner 验证各种场景
7️⃣ 发布到 Maven 私服或公共仓库
8️⃣ 用户只需引入 starter,即可开箱即用

💡 实际应用场景举例

假设你要封装一个 阿里云短信发送 SDK

  • 模块名:aliyun-sms-spring-boot-starter
  • autoconfigure 模块包含:
    • SmsClientAutoConfiguration
    • SmsProperties(accessKey, secret, signName...)
  • starter 模块引入:
    • autoconfigure
    • 阿里云 SMS SDK
    • commons-lang3 等常用工具

用户只需:

xml 复制代码
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-sms-spring-boot-starter</artifactId>
</dependency>
yaml 复制代码
aliyun:
  sms:
    access-key: xxx
    secret: yyy
    sign-name: 企鹅科技

然后注入 SmsClient 就能发短信了 ✅


🎯 最后提醒

建议 说明
✅ 使用 @ConditionalOnMissingBean 让用户能轻松覆盖默认行为
✅ 把第三方依赖设为 <optional>true</optional> 避免强制引入不需要的库
✅ 不要用 @ComponentScan 扫描 autoconfigure 包 防止重复加载
✅ 提供清晰的文档和示例 帮助用户快速上手

如果你想,我可以为你生成一个 完整的自定义 Starter 示例项目模板(含 pom.xml、配置类、测试等),帮助你快速上手 😊

相关推荐
曹牧6 小时前
C#:数组不能使用Const修饰符
java·数据结构·算法
码事漫谈6 小时前
解决Python调用C++ DLL失败的问题:extern "C"的关键作用
后端
码事漫谈6 小时前
从「能用」到「可靠」:深入探讨C++异常安全
后端
码事漫谈6 小时前
深入理解 C++ 现代类型推导:从 auto 到 decltype 与完美转发
后端
码事漫谈6 小时前
当无符号与有符号整数相遇:C++中的隐式类型转换陷阱
后端
YA3336 小时前
java设计模式六、装饰器模式
java·设计模式·装饰器模式
回忆是昨天里的海7 小时前
k8s集群-节点间通信之安装kube-flannel插件
java·docker·kubernetes
信仰_2739932437 小时前
Mybatis-Spring重要组件介绍
java·spring·mybatis
盖世英雄酱581367 小时前
java深度调试【第二章通过堆栈分析性能瓶颈】
java·后端