摘要
本文将详细介绍自定义 Spring Boot Starter 的完整过程。要构建自定义 Starter,首先需掌握 Spring Boot 中 @Auto-configuration
以及相关注解的工作原理,同时了解 Spring Boot 提供的一系列条件注解。在具备这些知识基础后,再按照特定步骤完成自定义 Starter 的开发,最后对其进行测试。接下来,让我们基于官网文档,深入学习具体内容。
@AutoConfiguration
@AutoConfiguration
是 Spring Boot 3.0 引入的一个注解,用于标记自动配置类。自动配置类的主要作用是根据类路径中的依赖、配置属性等条件,自动为应用程序配置所需的 Bean。它替代了早期版本中的 @Configuration
和 @EnableAutoConfiguration
组合,让自动配置类的定义更加清晰和简洁。
代码示例
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
public class MyAutoConfiguration {
@Bean
public MyService myService() {
return new MyService();
}
}
在这个示例中,MyAutoConfiguration
类被标记为自动配置类,其中定义了一个 MyService
的 Bean。当 Spring Boot 应用启动时,会自动扫描并加载这个自动配置类,创建 MyService
的实例。
Spring Boot 自动配置类的配置
加载
- 我们需要在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件中,加入我们的自动配置类,每个配置类占一行,从而让Spring Boot 加载自动配置类。

-
可以使用
#
字符在该文件中添加注释。 -
若自动配置类不是顶级类,其类名应使用
$
分隔其与包含类,如com.example.Outer$NestedAutoConfiguration
。 -
自动配置类必须通过在
imports
文件中列出类名来加载。要确保它们定义在特定的包空间中,且永远不会成为组件扫描的目标。此外,自动配置类不应使用组件扫描来查找其他组件,而应使用特定的@Import
注解。
顺序设置
-
@AutoConfiguration
注解属性:若配置需要按特定顺序应用,可使用@AutoConfiguration
注解的before
、beforeName
、after
和afterName
属性,或使用专门的@AutoConfigureBefore
和@AutoConfigureAfter
注解。例如,若提供特定的 Web 配置,可能需要在WebMvcAutoConfiguration
之后应用该配置类。 -
@AutoConfigureOrder
注解:若要对彼此无直接关联的某些自动配置类进行排序,可使用@AutoConfigureOrder
注解。该注解与常规的@Order
注解语义相同,但专门用于自动配置类。 -
与标准的
@Configuration
类一样,自动配置类的应用顺序仅影响其定义 Bean 的顺序,而后续创建这些 Bean 的顺序不受影响,创建顺序由每个 Bean 的依赖关系和任何@DependsOn
关系决定。 -
带顺序的自动配置代码示例
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;// 第一个自动配置类,创建 ServiceOne
@AutoConfiguration
public class ServiceOneAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ServiceOne serviceOne() {
return new ServiceOne();
}
}// 第二个自动配置类,使用 @AutoConfiguration 的 after 属性,在 ServiceOneAutoConfiguration 之后配置
@AutoConfiguration(after = ServiceOneAutoConfiguration.class)
public class ServiceTwoAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ServiceTwo serviceTwo() {
return new ServiceTwo();
}
}// 第三个自动配置类,使用 @AutoConfigureOrder 注解进行排序
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.core.Ordered;@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@AutoConfiguration
public class ServiceThreeAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ServiceThree serviceThree() {
return new ServiceThree();
}
}
弃用与替换
-
在开发过程中,有时需要对自动配置类进行弃用并提供替代方案,例如更改自动配置类所在的包名。
-
由于自动配置类可能会在
before/after
排序和排除项中被引用,因此需要额外创建一个文件来告知 Spring Boot 如何处理替换操作。具体做法是创建一个名为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements
的文件,在其中指明旧类和新类之间的映射关系。替换示例:
person.wend.springbootlearnexample.config.MyAutoConfiguration=person.wend.springbootlearnexample.config.auto.MyAutoConfiguration

- 同时,
AutoConfiguration.imports
文件也需要更新,使其仅引用替换后的新类,以确保 Spring Boot 在进行自动配置时能正确加载最新的配置类。
条件注解
在进行自动配置时,SpringBoot 包含六类@Conditional注解。可通过对 @Configuration
类或单个 @Bean
方法添加这些注解,在自定义代码中复用。这些注解包括:
-
类条件注解(Class Conditions)
-
Bean 条件注解(Bean Conditions)
-
属性条件注解(Property Conditions)
-
资源条件注解(Resource Conditions)
-
Web 应用条件注解(Web Application Conditions)
-
SpEL 表达式条件注解(SpEL Expression Conditions)
类条件注解
-
@ConditionalOnClass:@ConditionalOnClass
注解用于指定只有当类路径中存在指定的类时,被注解的配置类或@Bean
方法才会生效。这在需要根据特定依赖是否存在来决定是否进行配置时非常有用。例如,当项目引入了某个库时,才加载与之相关的配置。 -
@ConditionalOnMissingClass:@ConditionalOnMissingClass
注解与@ConditionalOnClass
注解相反,它用于指定只有当类路径中不存在指定的类时,被注解的配置类或@Bean
方法才会生效。这在需要避免某些配置在特定类存在时生效的场景中非常有用。 -
示例代码
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;// 模拟某个库中的类
class SomeLibraryClass {}@Configuration
@ConditionalOnClass(SomeLibraryClass.class)
public class MyConfig {@Bean public MyService myService() { return new MyService(); }
}
class MyService {}
-
@ConditionalOnClass
和@ConditionalOnMissingClass
注解通过检查类路径中特定类的存在与否,为 Spring Boot 应用的配置提供了灵活的条件加载机制,有助于根据不同的依赖环境进行动态配置。
Bean 条件注解
-
@ConditionalOnBean
和@ConditionalOnMissingBean
注解可根据特定 Bean 是否存在来决定是否包含某个 Bean。 -
使用
value
属性按类型指定 Bean -
使用
name
属性按名称指定 Bean -
search
属性可用于限制在搜索 Bean 时应考虑的ApplicationContext
层次结构 -
示例代码
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;@AutoConfiguration
public class MyAutoConfiguration {@Bean @ConditionalOnMissingBean public SomeService someService() { return new SomeService(); }
}
-
在上述示例中,如果
ApplicationContext
中尚未包含SomeService
类型的 Bean,那么someService
Bean 将会被创建。
属性条件注解
-
@ConditionalOnProperty
注解可根据 Spring 环境属性来决定是否包含配置。使用prefix
和name
属性指定要检查的属性。默认情况下,任何存在且不等于false
的属性都会匹配。还可以使用havingValue
和matchIfMissing
属性进行更高级的检查。若name
属性中指定了多个名称,则所有属性都必须通过测试条件才会匹配。 -
代码示例
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class PropertyConfig {@Bean @ConditionalOnProperty(prefix = "myapp", name = "enabled", havingValue = "true") public MyService myService() { return new MyService(); }
}
class MyService {}
-
在
application.properties
中配置myapp.enabled=true
时,myService
Bean 才会被创建。
资源条件注解
-
@ConditionalOnResource
注解只有在特定资源存在时才会包含配置。可以使用 Spring 常用约定指定资源,如file:/home/user/test.dat
。import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ResourceConfig {@Bean @ConditionalOnResource(resources = "classpath:test.properties") public ResourceService resourceService() { return new ResourceService(); }
}
class ResourceService {}
-
当类路径下存在
test.properties
文件时,resourceService
Bean 才会被创建。
Web 应用条件注解
-
@ConditionalOnWebApplication
和@ConditionalOnNotWebApplication
注解根据应用是否为 Web 应用来决定是否包含配置。基于 Servlet 的 Web 应用是指使用Spring WebApplicationContext
、定义了会话作用域或具有ConfigurableWebEnvironment
的应用;响应式 Web 应用是指使用ReactiveWebApplicationContext
或具有ConfigurableReactiveWebEnvironment
的应用。 -
@ConditionalOnWarDeployment
和@ConditionalOnNotWarDeployment
注解根据应用是否为部署到 Servlet 容器的传统 WAR 应用来决定是否包含配置。对于使用嵌入式 Web 服务器运行的应用,此条件不匹配。import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class WebAppConfig {@Bean @ConditionalOnWebApplication public WebService webService() { return new WebService(); }
}
class WebService {}
-
当应用是 Web 应用时,
webService
Bean 才会被创建。
SpEL 表达式条件注解
-
@ConditionalOnExpression
注解根据 SpEL 表达式的结果来决定是否包含配置。但在表达式中引用 Bean 会导致该 Bean 在上下文刷新处理的早期初始化,从而使该 Bean 无法进行后处理(如配置属性绑定),其状态可能不完整。import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class SpELConfig {@Bean @ConditionalOnExpression("#{systemProperties['java.version'].startsWith('17')}") public Java17Service java17Service() { return new Java17Service(); }
}
class Java17Service {}
-
当 Java 版本以
17
开头时,java17Service
Bean 才会被创建。
测试自动配置
自动配置受用户配置(如@Bean
定义和Environment
自定义)、条件评估(特定库是否存在)等因素影响。每个测试都应创建一个定义良好的ApplicationContext
。ApplicationContextRunner
是实现此目标的好方法。
ApplicationContextRunner
-
ApplicationContextRunner通常被定义为测试类的一个字段,用于收集基本的通用配置。以下示例确保
MyServiceAutoConfiguration
始终调用:private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
-
若有多个自动配置,无需排序,调用顺序与运行应用时相同。
-
每个测试可用运行器表示特定用例,如调用用户配置(
UserConfiguration
)并检查自动配置是否正确退出,@Test
方法中使用withUserConfiguration
和run
回调上下文结合AssertJ
进行断言。@Test
void defaultServiceBacksOff() {
this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
});
}@Configuration(proxyBeanMethods = false) static class UserConfiguration { @Bean MyService myCustomService() { return new MyService("mine"); } }
-
可轻松自定义
Environment
,如withPropertyValues
方法设置属性值并进行断言。@Test
void serviceNameCanBeConfigured() {
this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
});
} -
运行器可用于显示
ConditionEvaluationReport
,通过ConditionEvaluationReportLoggingListener
设置日志级别打印报告。class MyConditionEvaluationReportingTests {
@Test void autoConfigTest() { new ApplicationContextRunner() .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) .run((context) -> { // Test something... }); }
}
模拟 Web 环境
若测试仅在 servlet 或反应式 Web 应用程序上下文中运行的自动配置,可分别使用WebApplicationContextRunner
或ReactiveWebApplicationContextRunner
。
-
WebApplicationContextRunner代码示例:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.test.StepVerifier;public class ReactiveAutoConfigurationTest {
private final ReactiveWebApplicationContextRunner reactiveWebContextRunner = new ReactiveWebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyReactiveAutoConfiguration.class)); @Test void testReactiveAutoConfiguration() { reactiveWebContextRunner.run((ApplicationContext context) -> { // 断言上下文中存在 MyReactiveService 的 Bean assertThat(context).hasSingleBean(MyReactiveService.class); MyReactiveService myReactiveService = context.getBean(MyReactiveService.class); // 调用反应式服务类的方法并使用 StepVerifier 进行断言 StepVerifier.create(myReactiveService.sayHello()) .expectNext("Hello from MyReactiveService in Reactive context") .verifyComplete(); }); }
}
-
ReactiveWebApplicationContextRunner代码示例:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.test.StepVerifier;public class ReactiveAutoConfigurationTest {
private final ReactiveWebApplicationContextRunner reactiveWebContextRunner = new ReactiveWebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyReactiveAutoConfiguration.class)); @Test void testReactiveAutoConfiguration() { reactiveWebContextRunner.run((ApplicationContext context) -> { // 断言上下文中存在 MyReactiveService 的 Bean assertThat(context).hasSingleBean(MyReactiveService.class); MyReactiveService myReactiveService = context.getBean(MyReactiveService.class); // 调用反应式服务类的方法并使用 StepVerifier 进行断言 StepVerifier.create(myReactiveService.sayHello()) .expectNext("Hello from MyReactiveService in Reactive context") .verifyComplete(); }); }
}
覆盖类路径
使用FilteredClassLoader
可测试运行时特定类和 / 或包不存在时的情况,如通过withClassLoader
方法设置类加载器并断言自动配置是否正确禁用。
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
创建自定义Starter的步骤
典型的 Spring Boot Starter 包含用于自动配置和定制特定技术(以 "acme" 为例)基础设施的代码。为实现易于扩展,会在专用命名空间中向环境暴露多个配置键,并且提供单一的 "starter" 依赖,方便用户快速上手。
- acme:"acme" 是一个占位符,用来代表任意一种技术、框架、库或者服务。"acme" 代表开发者想要自动配置和定制其基础设施的目标技术。例如,这个技术可能是 Redis、MongoDB、RabbitMQ 等,"acme" 在这里可以理解成一个泛指的、可以被替换为具体技术的抽象概念。
自定义 Starter 的组成
-
自动配置模块(autoconfigure module):包含针对 "acme" 的自动配置代码。
-
Starter 模块:依赖于自动配置模块,同时包含 "acme" 以及其他常用的额外依赖。简而言之,添加该 Starter 应能提供使用相应库所需的一切。
模块分离的考量
-
分离的优势:将自动配置和 Starter 分成两个模块并非必要,但当 "acme" 存在多种变体、选项或可选功能时,进行分离是更好的选择。这样可以明确表明某些功能是可选的,并且能够定制一个对这些可选依赖有特定选择的 Starter。同时,其他人可以仅依赖自动配置模块,按照自己的需求定制 Starter。
-
合并的情况:如果自动配置相对简单,且没有可选功能,将两个模块合并到 Starter 中也是可行的。
Starter 的命名规则
-
官方Starter:Spring-Boot 官方的Starter 都统一以 spring-boot-starter-* 格式命名,其中
*
是特定类型的应用程序。此命名结构旨在帮助您在需要查找启动器时使用。许多 IDE 中的 Maven 集成允许您按名称搜索依赖项。例如,安装了适当的 Eclipse 或 Spring Tools 插件后,您可以按ctrl-space
POM 编辑器并键入"spring-boot-starter"以获取完整列表。 -
三方库或自建Starter:第三方启动器不应以
spring-boot
开头,因为它是为官方 Spring Boot 工件保留的。通常正确的写法是,名为thirdpartyproject
的第三方启动器项目,通常其定义的Starter应命名为thirdpartyproject-spring-boot-starter
。
配置键
命名空间使用规范
如果自定义的 Starter 提供配置键,要使用独特的命名空间,切勿将配置键置于 Spring Boot 已使用的命名空间(如 server
、management
、spring
等)中。因为后续 Spring Boot 可能会修改这些命名空间,从而导致模块出现问题。一般来说,应使用自己拥有的命名空间作为配置键的前缀,例如 acme
。
- 配置示例
acme.my-project.person.first-name=John acme.my-project.person.last-name=Smith acme.my-project.person.version=1.0.0
配置键文档化
-
使用
@ConfigurationProperties
注解类:为保证配置键有文档记录,需为@ConfigurationProperties
注解类中的每个属性添加字段 Javadoc。@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 ... public boolean isCheckLocation() { return this.checkLocation; } public void setCheckLocation(boolean checkLocation) { this.checkLocation = checkLocation; } public Duration getLoginTimeout() { return this.loginTimeout; } public void setLoginTimeout(Duration loginTimeout) { this.loginTimeout = loginTimeout; }
}
-
配置示例展示了
AcmeProperties
类,包含checkLocation
和loginTimeout
等属性,并为每个属性添加了说明性的 Javadoc。 -
记录类的处理方式:若使用
@ConfigurationProperties
注解记录类(record class),需通过类级别的 Javadoc 标签@param
来提供记录组件的描述,因为记录类没有显式的实例字段来放置常规的字段级 Javadoc。
描述规范
为确保描述的一致性,需遵循以下规则:
-
描述开头不要使用 "The" 或 "A"。
-
对于布尔类型,描述以 "Whether" 或 "Enable" 开头。
-
对于基于集合的类型,描述以 "Comma - separated list" 开头。
-
使用
Duration
类型而非long
类型,若默认单位不是毫秒,需描述默认单位,如 "If a duration suffix is not specified, seconds will be used"。 -
除非默认值需在运行时确定,否则描述中不要提供默认值。
元数据生成
要触发元数据生成,以便 IDE 能为配置键提供辅助功能。可以查看生成的元数据文件 META - INF/spring-configuration-metadata.json
,确保配置键有恰当的文档记录。在兼容的 IDE 中使用自定义的 Starter 也是验证元数据质量的好方法。
"autoconfigure" 模块
"autoconfigure" 模块包含了使用某个库所需的一切必要内容。它可能包含配置键定义(例如使用 @ConfigurationProperties
注解),以及可用于进一步自定义组件初始化方式的回调接口。
-
依赖设置:应该将对该库的依赖标记为可选的。这样做可以更轻松地将 "autoconfigure" 模块包含到项目中。因为当依赖被标记为可选时,库本身不会被提供,默认情况下 Spring Boot 会跳过对相关自动配置的加载。
-
在 Maven 项目中,通过在
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>your-library</artifactId> <version>1.0.0</version> <optional>true</optional> </dependency> </dependencies>pom.xml
文件里的<dependency>
标签中添加<optional>true</optional>
来将依赖标记为可选。
-
-
自动配置元数据文件:Spring Boot 使用注解处理器将自动配置的条件收集到一个元数据文件(
META - INF/spring-autoconfigure-metadata.properties
)中。如果该文件存在,Spring Boot 会使用它来提前过滤掉不匹配的自动配置,从而提高应用的启动时间。 -
Maven 构建配置:在使用 Maven 进行项目构建时,需要将编译器插件(版本 3.12.0 或更高)配置为把
<project> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure-processor</artifactId> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> </project>spring-boot-autoconfigure-processor
添加到注解处理器路径中。
Starter 模块的本质与作用
Starter 本质上是一个空的 JAR 文件,其主要作用是提供使用相关库所需的必要依赖,可被视为关于启动使用该库所需依赖的一种推荐组合。
- spring-boot-starter 示例:

-
Starter 依赖添加原则:
-
避免主观臆断:当你开发一个 Spring Boot Starter 时,不要想当然地认为引入这个 Starter 的项目是什么样的情况。比如说,不能觉得所有用你这个 Starter 的项目都有某些特定的配置或者已经引入了某些其他库。如果这个 Starter 所自动配置的库在正常使用的时候还需要其他的 Starter 配合,那你就得把这些额外需要的 Starter 清楚地告诉别人。
-
合理选择默认依赖:要是你要自动配置的库有很多可以选择的依赖,这时候选一套合适的默认依赖就挺难办的。你得保证 Starter 里只放那些在大家平时用这个库的时候一定会用到的依赖,那些可有可无的依赖就别放进去了。
-
-
Starter 依赖要求
- Starter 模块必须直接或间接引用 Spring Boot 核心 Starter(
spring-boot- starter
)。若项目仅使用自定义 Starter 创建,由于核心 Starter 的存在,Spring Boot 的核心功能仍能正常使用。如果自定义 Starter 依赖于其他已包含核心 Starter 的 Starter,则无需再额外添加核心 Starter 依赖。
- Starter 模块必须直接或间接引用 Spring Boot 核心 Starter(