此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Boot 3.4.3! |
创建您自己的自动配置
如果您在开发共享库的公司工作,或者如果您从事开源或商业库的工作,则可能需要开发自己的自动配置。 自动配置类可以捆绑在外部 jar 中,并且仍然由 Spring Boot 拾取。
Auto-configuration 可以与 “starter” 相关联,该 “starter” 提供 auto-configuration 代码以及您将与之一起使用的典型 libraries。 我们首先介绍构建自己的自动配置所需了解的内容,然后我们继续介绍创建自定义Starters所需的典型步骤。
了解自动配置的 Bean
实现自动配置的类用@AutoConfiguration
.
此注解本身使用@Configuration
,使自动配置成为标准@Configuration
类。
附加@Conditional
注释用于限制何时应应用自动配置。
通常,自动配置类使用@ConditionalOnClass
和@ConditionalOnMissingBean
附注。
这可确保仅在找到相关类且您尚未声明自己的类时应用自动配置@Configuration
.
查找 Auto-configuration Candidate
Spring Boot 检查是否存在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件。
该文件应列出您的配置类,每行一个类名,如以下示例所示:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
您可以使用该字符向 imports 文件添加注释。# |
在自动配置类不是顶级类的不常见情况下,应该使用它的类名将其与包含类分开,例如$ com.example.Outer$NestedAutoConfiguration . |
自动配置只能通过在 imports 文件中命名来加载。
确保它们在特定的包空间中定义,并且它们永远不会成为组件扫描的目标。
此外,自动配置类不应启用组件扫描来查找其他组件。
特定@Import 应改用 annotations。 |
如果您的配置需要按特定顺序应用,您可以使用before
,beforeName
,after
和afterName
attributes 上的@AutoConfiguration
注解或专用的@AutoConfigureBefore
和@AutoConfigureAfter
附注。
例如,如果您提供特定于 Web 的配置,则可能需要在WebMvcAutoConfiguration
.
如果您想订购某些不应直接了解彼此的自动配置,您还可以使用@AutoConfigureOrder
.
该注解与常规@Order
注解,但为自动配置类提供专用顺序。
与标准一样@Configuration
类,则应用自动配置类的顺序仅影响其 bean 的定义顺序。
随后创建这些 bean 的顺序不受影响,并且由每个 bean 的依赖项和任何@DependsOn
关系。
弃用和替换自动配置类
您可能需要偶尔弃用 auto-configuration 类并提供替代方案。 例如,您可能希望更改 auto-configuration 类所在的软件包名称。
由于自动配置类可以在before
/after
ordering 和excludes
,你需要添加一个额外的文件,告诉 Spring Boot 如何处理替换。
要定义替换项,请创建一个META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements
文件,指示旧类和新类之间的链接。
例如:
com.mycorp.libx.autoconfigure.LibXAutoConfiguration=com.mycorp.libx.autoconfigure.core.LibXAutoConfiguration
这AutoConfiguration.imports file 也应更新为仅引用 replacement 类。 |
条件注释
您几乎总是希望包含一个或多个@Conditional
annotations 的 auto-configuration 类。
这@ConditionalOnMissingBean
annotation 是一个常见的示例,用于允许开发人员在对您的默认值不满意时覆盖自动配置。
Spring Boot 包括许多@Conditional
注解,您可以通过注解@Configuration
类或个人@Bean
方法。
这些注释包括:
类条件
这@ConditionalOnClass
和@ConditionalOnMissingClass
annotations 让@Configuration
根据特定类的存在与否来包含类。
由于注释元数据是使用 ASM 解析的,因此您可以使用value
属性来引用实际类,即使该类实际上可能没有出现在正在运行的应用程序类路径上。
您还可以使用name
属性(如果您希望使用String
价值。
此机制不会以相同的方式应用于@Bean
方法,其中返回类型通常是条件的目标:在方法的条件应用之前,JVM 将加载类和可能处理的方法引用,如果类不存在,则这些引用将失败。
为了处理这种情况,单独的@Configuration
class 可用于隔离条件,如以下示例所示:
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
// Some conditions ...
public class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService.class)
public static class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
}
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
// Some conditions ...
class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService::class)
class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
}
If you use @ConditionalOnClass
or @ConditionalOnMissingClass
as a part of a meta-annotation to compose your own composed annotations, you must use name
as referring to the class in such a case is not handled.
Bean Conditions
The @ConditionalOnBean
and @ConditionalOnMissingBean
annotations let a bean be included based on the presence or absence of specific beans.
You can use the value
attribute to specify beans by type or name
to specify beans by name.
The search
attribute lets you limit the ApplicationContext
hierarchy that should be considered when searching for beans.
When placed on a @Bean
method, the target type defaults to the return type of the method, as shown in the following example:
-
Java
-
Kotlin
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();
}
}
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
fun someService(): SomeService {
return SomeService()
}
}
In the preceding example, the someService
bean is going to be created if no bean of type SomeService
is already contained in the ApplicationContext
.
You need to be very careful about the order in which bean definitions are added, as these conditions are evaluated based on what has been processed so far.
For this reason, we recommend using only @ConditionalOnBean
and @ConditionalOnMissingBean
annotations on auto-configuration classes (since these are guaranteed to load after any user-defined bean definitions have been added).
@ConditionalOnBean
and @ConditionalOnMissingBean
do not prevent @Configuration
classes from being created.
The only difference between using these conditions at the class level and marking each contained @Bean
method with the annotation is that the former prevents registration of the @Configuration
class as a bean if the condition does not match.
When declaring a @Bean
method, provide as much type information as possible in the method’s return type.
For example, if your bean’s concrete class implements an interface the bean method’s return type should be the concrete class and not the interface.
Providing as much type information as possible in @Bean
methods is particularly important when using bean conditions as their evaluation can only rely upon to type information that is available in the method signature.
Property Conditions
The @ConditionalOnProperty
annotation lets configuration be included based on a Spring Environment property.
Use the prefix
and name
attributes to specify the property that should be checked.
By default, any property that exists and is not equal to false
is matched.
There is also a dedicated @ConditionalOnBooleanProperty
annotation specifically made for boolean properties.
With both annotations you can also create more advanced checks by using the havingValue
and matchIfMissing
attributes.
If multiple names are given in the name
attribute, all of the properties have to pass the test for the condition to match.
Resource Conditions
The @ConditionalOnResource
annotation lets configuration be included only when a specific resource is present.
Resources can be specified by using the usual Spring conventions, as shown in the following example: file:/home/user/test.dat
.
Web Application Conditions
The @ConditionalOnWebApplication
and @ConditionalOnNotWebApplication
annotations let configuration be included depending on whether the application is a web application.
A servlet-based web application is any application that uses a Spring WebApplicationContext
, defines a session
scope, or has a ConfigurableWebEnvironment
.
A reactive web application is any application that uses a ReactiveWebApplicationContext
, or has a ConfigurableReactiveWebEnvironment
.
The @ConditionalOnWarDeployment
and @ConditionalOnNotWarDeployment
annotations let configuration be included depending on whether the application is a traditional WAR application that is deployed to a servlet container.
This condition will not match for applications that are run with an embedded web server.
SpEL Expression Conditions
The @ConditionalOnExpression
annotation lets configuration be included based on the result of a SpEL expression.
Referencing a bean in the expression will cause that bean to be initialized very early in context refresh processing.
As a result, the bean won’t be eligible for post-processing (such as configuration properties binding) and its state may be incomplete.
Testing your Auto-configuration
An auto-configuration can be affected by many factors: user configuration (@Bean
definition and Environment
customization), condition evaluation (presence of a particular library), and others.
Concretely, each test should create a well defined ApplicationContext
that represents a combination of those customizations.
ApplicationContextRunner
provides a great way to achieve that.
ApplicationContextRunner
doesn’t work when running the tests in a native image.
ApplicationContextRunner
is usually defined as a field of the test class to gather the base, common configuration.
The following example makes sure that MyServiceAutoConfiguration
is always invoked:
-
Java
-
Kotlin
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
If multiple auto-configurations have to be defined, there is no need to order their declarations as they are invoked in the exact same order as when running the application.
Each test can use the runner to represent a particular use case.
For instance, the sample below invokes a user configuration (UserConfiguration
) and checks that the auto-configuration backs off properly.
Invoking run
provides a callback context that can be used with AssertJ.
-
Java
-
Kotlin
@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");
}
}
@Test
fun defaultServiceBacksOff() {
contextRunner.withUserConfiguration(UserConfiguration::class.java)
.run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context).getBean("myCustomService")
.isSameAs(context.getBean(MyService::class.java))
}
}
@Configuration(proxyBeanMethods = false)
internal class UserConfiguration {
@Bean
fun myCustomService(): MyService {
return MyService("mine")
}
}
It is also possible to easily customize the Environment
, as shown in the following example:
-
Java
-
Kotlin
@Test
void serviceNameCanBeConfigured() {
this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
});
}
@Test
fun serviceNameCanBeConfigured() {
contextRunner.withPropertyValues("user.name=test123").run { context: AssertableApplicationContext ->
assertThat(context).hasSingleBean(MyService::class.java)
assertThat(context.getBean(MyService::class.java).name).isEqualTo("test123")
}
}
The runner can also be used to display the ConditionEvaluationReport
.
The report can be printed at INFO
or DEBUG
level.
The following example shows how to use the ConditionEvaluationReportLoggingListener
to print the report in auto-configuration tests.
-
Java
-
Kotlin
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class MyConditionEvaluationReportingTests {
@Test
void autoConfigTest() {
new ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
import org.springframework.boot.logging.LogLevel
import org.springframework.boot.test.context.assertj.AssertableApplicationContext
import org.springframework.boot.test.context.runner.ApplicationContextRunner
class MyConditionEvaluationReportingTests {
@Test
fun autoConfigTest() {
ApplicationContextRunner()
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
.run { context: AssertableApplicationContext? -> }
}
}
Simulating a Web Context
If you need to test an auto-configuration that only operates in a servlet or reactive web application context, use the WebApplicationContextRunner
or ReactiveWebApplicationContextRunner
respectively.
Overriding the Classpath
It is also possible to test what happens when a particular class and/or package is not present at runtime.
Spring Boot ships with a FilteredClassLoader
that can easily be used by the runner.
In the following example, we assert that if MyService
is not present, the auto-configuration is properly disabled:
-
Java
-
Kotlin
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
@Test
fun serviceIsIgnoredIfLibraryIsNotPresent() {
contextRunner.withClassLoader(FilteredClassLoader(MyService::class.java))
.run { context: AssertableApplicationContext? ->
assertThat(context).doesNotHaveBean("myService")
}
}
Creating Your Own Starter
A typical Spring Boot starter contains code to auto-configure and customize the infrastructure of a given technology, let’s call that "acme".
To make it easily extensible, a number of configuration keys in a dedicated namespace can be exposed to the environment.
Finally, a single "starter" dependency is provided to help users get started as easily as possible.
Concretely, a custom starter can contain the following:
-
The autoconfigure
module that contains the auto-configuration code for "acme".
-
The starter
module that provides a dependency to the autoconfigure
module as well as "acme" and any additional dependencies that are typically useful.
In a nutshell, adding the starter should provide everything needed to start using that library.
This separation in two modules is in no way necessary.
If "acme" has several flavors, options or optional features, then it is better to separate the auto-configuration as you can clearly express the fact some features are optional.
Besides, you have the ability to craft a starter that provides an opinion about those optional dependencies.
At the same time, others can rely only on the autoconfigure
module and craft their own starter with different opinions.
If the auto-configuration is relatively straightforward and does not have optional features, merging the two modules in the starter is definitely an option.
Naming
You should make sure to provide a proper namespace for your starter.
Do not start your module names with spring-boot
, even if you use a different Maven groupId
.
We may offer official support for the thing you auto-configure in the future.
As a rule of thumb, you should name a combined module after the starter.
For example, assume that you are creating a starter for "acme" and that you name the auto-configure module acme-spring-boot
and the starter acme-spring-boot-starter
.
If you only have one module that combines the two, name it acme-spring-boot-starter
.
Configuration keys
If your starter provides configuration keys, use a unique namespace for them.
In particular, do not include your keys in the namespaces that Spring Boot uses (such as server
, management
, spring
, and so on).
If you use the same namespace, we may modify these namespaces in the future in ways that break your modules.
As a rule of thumb, prefix all your keys with a namespace that you own (for example acme
).
Make sure that configuration keys are documented by adding field Javadoc for each property, as shown in the following example:
-
Java
-
Kotlin
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@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;
}
}
import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.Duration
@ConfigurationProperties("acme")
class AcmeProperties(
/**
* Whether to check the location of acme resources.
*/
var isCheckLocation: Boolean = true,
/**
* Timeout for establishing a connection to the acme server.
*/
var loginTimeout:Duration = Duration.ofSeconds(3))
You should only use plain text with @ConfigurationProperties
field Javadoc, since they are not processed before being added to the JSON.
If you use @ConfigurationProperties
with record class then record components' descriptions should be provided via class-level Javadoc tag @param
(there are no explicit instance fields in record classes to put regular field-level Javadocs on).
Here are some rules we follow internally to make sure descriptions are consistent:
-
Do not start the description by "The" or "A".
-
For boolean
types, start the description with "Whether" or "Enable".
-
For collection-based types, start the description with "Comma-separated list"
-
Use Duration
rather than long
and describe the default unit if it differs from milliseconds, such as "If a duration suffix is not specified, seconds will be used".
-
Do not provide the default value in the description unless it has to be determined at runtime.
Make sure to trigger meta-data generation so that IDE assistance is available for your keys as well.
You may want to review the generated metadata (META-INF/spring-configuration-metadata.json
) to make sure your keys are properly documented.
Using your own starter in a compatible IDE is also a good idea to validate that quality of the metadata.
The “autoconfigure” Module
The autoconfigure
module contains everything that is necessary to get started with the library.
It may also contain configuration key definitions (such as @ConfigurationProperties
) and any callback interface that can be used to further customize how the components are initialized.
You should mark the dependencies to the library as optional so that you can include the autoconfigure
module in your projects more easily.
If you do it that way, the library is not provided and, by default, Spring Boot backs off.
Spring Boot uses an annotation processor to collect the conditions on auto-configurations in a metadata file (META-INF/spring-autoconfigure-metadata.properties
).
If that file is present, it is used to eagerly filter auto-configurations that do not match, which will improve startup time.
When building with Maven, configure the compiler plugin (3.12.0 or later) to add spring-boot-autoconfigure-processor
to the annotation processor paths:
<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>
With Gradle, the dependency should be declared in the annotationProcessor
configuration, as shown in the following example:
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
Starter Module
The starter is really an empty jar.
Its only purpose is to provide the necessary dependencies to work with the library.
You can think of it as an opinionated view of what is required to get started.
Do not make assumptions about the project in which your starter is added.
If the library you are auto-configuring typically requires other starters, mention them as well.
Providing a proper set of default dependencies may be hard if the number of optional dependencies is high, as you should avoid including dependencies that are unnecessary for a typical usage of the library.
In other words, you should not include optional dependencies.
Either way, your starter must reference the core Spring Boot starter (spring-boot-starter
) directly or indirectly (there is no need to add it if your starter relies on another starter).
If a project is created with only your custom starter, Spring Boot’s core features will be honoured by the presence of the core starter.