# 7.12. 创建您自己的自动配置

如果您在一家开发共享库的公司工作，或者如果您在开源或商业库中工作，您可能想要开发自己的自动配置。自动配置类可以捆绑在外部 jar 中，并且仍然可以被 Spring Boot 拾取。

自动配置可以与提供自动配置代码以及您将使用的典型库的“启动器”相关联。我们首先介绍了构建您自己的自动配置所需了解的内容，然后我们继续介绍[创建自定义启动器所需的典型步骤](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.custom-starter)。 一个[演示项目](https://github.com/snicoll-demos/spring-boot-master-auto-configuration)可用于展示如何逐步创建启动器。

#### 7.12.1.了解自动配置的 Bean

在幕后，自动配置是通过`@AutoConfiguration`注解实现的。这个注解本身是用 `@Configuration`元注解的，可以使自动配置成为标准`@Configuration`类。添加`@Conditional`注解用于限制何时应用自动配置。通常，自动配置类使用`@ConditionalOnClass`和`@ConditionalOnMissingBean`注解。这确保了自动配置仅在找到相关类并且您没有声明自己的类时适用`@Configuration`。

您可以浏览源代码[`spring-boot-autoconfigure`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure)以查看`@Configuration`Spring 提供的类（参见[`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)文件）。

#### 7.12.2. 定位自动配置候选

Spring Boot 检查发布的 jar中是否存在`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`文件。该文件应列出您的配置类，如以下示例所示：

```
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
```

您可以通过在此文件中使用`#`注释。 *自动配置只能* 以这种方式加载。确保它们是在特定的包空间中定义的，并且它们永远不是组件扫描的目标。此外，自动配置类不应启用组件扫描以查找其他组件。应该使用 特定的 `@Import`来代替。

如果您的配置需要按特定顺序应用，您可以使用[`@AutoConfigureAfter`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureAfter.java)或[`@AutoConfigureBefore`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfigureBefore.java)注解。例如，如果您提供特定于 Web 的配置，您的`WebMvcAutoConfiguration`类可能需要在.

如果您使用[`@AutoConfiguration`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/AutoConfiguration.java)注解，则可以使用`before`、 `beforeName`、`after`和`afterName`属性别名来代替专用注解。 如果您想订购某些彼此不应该有任何直接了解的自动配置，您也可以使用`@AutoConfigureOrder`. 该注解与常规注解具有相同的语义，但`@Order`为自动配置类提供了专用顺序。

与标准`@Configuration`类一样，应用自动配置类的顺序只影响定义它们的 bean 的顺序。随后创建这些 bean 的顺序不受影响，由每个 bean 的依赖关系和任何`@DependsOn`定义的关系决定。

#### 7.12.3. 条件注解

您几乎总是希望`@Conditional`在您的自动配置类中包含一个或多个注解。注解是一个常见的`@ConditionalOnMissingBean`例子，如果开发人员对你的默认设置不满意，它可以让他们覆盖自动配置。

Spring Boot 包含许多注解，您可以通过注解类或单个方法`@Conditional`在自己的代码中重用它们。这些注解包括：`@Configuration` `@Bean`

* [类条件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations.class-conditions)
* [bean条件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations.bean-conditions)
* [属性条件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations.property-conditions)
* [资源条件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations.resource-conditions)
* [网络应用条件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations.web-application-conditions)
* [SpEL 表达式条件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations.spel-conditions)

**7.12.3.1.class条件**

`@ConditionalOnClass`和`@ConditionalOnMissingClass`注解允许根据`@Configuration`特定类的存在与否来包含类。由于注解元数据是使用[ASM](https://asm.ow2.io/)解析的，因此您可以使用该`value`属性来引用真实的类，即使该类实际上可能不会出现在正在运行的应用程序类路径中。`name`如果您更喜欢使用值指定类名，也可以使用该属性`String`。

此机制不适用于`@Bean`通常返回类型是条件目标的方法：在方法上的条件适用之前，JVM 将加载类和可能处理的方法引用，如果类不是，则这些方法引用将失败当下。

为了处理这种情况，可以使用一个单独的`@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();
        }
​
    }
​
}
```

如果您使用`@ConditionalOnClass`或`@ConditionalOnMissingClass`作为元注解的一部分来组成您自己的组合注解，则必须`name`在不处理这种情况下使用 as 引用类。

**7.12.3.2. bean条件**

`@ConditionalOnBean`和注解允许根据`@ConditionalOnMissingBean`特定 bean 的存在或不存在来包含 bean。您可以使用该`value`属性按类型`name`指定bean 或按名称指定bean。该`search`属性允许您限制`ApplicationContext`在搜索 bean 时应考虑的层次结构。

放置在`@Bean`方法上时，目标类型默认为方法的返回类型，如下例所示：

```
@AutoConfiguration
public class MyAutoConfiguration {
​
    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }
​
}
```

在前面的示例中，`someService`如果.`SomeService``ApplicationContext` 您需要非常小心添加 bean 定义的顺序，因为这些条件是根据到目前为止已处理的内容进行评估的。出于这个原因，我们建议仅在自动配置类上使用`@ConditionalOnBean`和`@ConditionalOnMissingBean`注解（因为保证在添加任何用户定义的 bean 定义后加载这些注解）。 `@ConditionalOnBean`并且`@ConditionalOnMissingBean`不要阻止`@Configuration`创建类。在类级别使用这些条件和用`@Bean`注解标记每个包含的方法之间的唯一区别是，如果条件不匹配 ，前者会阻止将类注册为 bean。`@Configuration` 声明`@Bean`方法时，在方法的返回类型中提供尽可能多的类型信息。例如，如果你的 bean 的具体类实现了一个接口，那么 bean 方法的返回类型应该是具体类而不是接口。在使用 bean 条件时，在方法中提供尽可能多的类型信息`@Bean`尤为重要，因为它们的评估只能依赖于方法签名中可用的类型信息。

**7.12.3.3. 属性条件**

`@ConditionalOnProperty`注解允许基于 Spring Environment 属性包含配置。使用`prefix`和`name`属性指定应检查的属性。默认情况下，匹配任何存在但不等于`false`的属性。您还可以使用`havingValue`和`matchIfMissing`属性创建更高级的检查。

**7.12.3.4.资源条件**

`@ConditionalOnResource`注解允许仅在存在特定资源时才包含配置。可以使用通常的 Spring 约定来指定资源，如下例所示：`file:/home/user/test.dat`

**7.12.3.5.网络应用条件**

`@ConditionalOnWebApplication`和注解允许根据`@ConditionalOnNotWebApplication`应用程序是否为“Web 应用程序”来包含配置。基于 servlet 的 Web 应用程序是任何使用 Spring `WebApplicationContext`、定义`session`范围或具有`ConfigurableWebEnvironment`. 反应式 Web 应用程序是任何使用`ReactiveWebApplicationContext`或具有`ConfigurableReactiveWebEnvironment`.

注解允许根据`@ConditionalOnWarDeployment`应用程序是否是部署到容器的传统 WAR 应用程序来包含配置。对于使用嵌入式服务器运行的应用程序，此条件将不匹配。

**7.12.3.6.SpEL 表达条件**

`@ConditionalOnExpression`注解允许基于[SpEL 表达式](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/core.html#expressions)的结果包含配置。

在表达式中引用 bean 将导致该 bean 在上下文刷新处理中很早就被初始化。结果，bean 将不适合进行后处理（例如配置属性绑定），并且其状态可能不完整。

#### 7.12.4.测试您的自动配置

自动配置可能受到许多因素的影响：用户配置（定义`@Bean`和定制`Environment`）、条件评估（特定库的存在）等。具体来说，每个测试都应该创建一个定义良好`ApplicationContext`的，代表这些定制的组合。 `ApplicationContextRunner`提供了实现这一目标的好方法。

`ApplicationContextRunner`通常被定义为测试类的一个字段，用于收集基本的、通用的配置。以下示例确保始终调用`MyServiceAutoConfiguration`：

```
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
        .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
```

如果必须定义多个自动配置，则无需对它们的声明进行排序，因为它们的调用顺序与运行应用程序时完全相同。 每个测试都可以使用运行器来表示特定的用例。例如，下面的示例调用了用户配置 ( `UserConfiguration`) 并检查自动配置是否正确退出。调用`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`，如以下示例所示：

```
@Test
void serviceNameCanBeConfigured() {
    this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
    });
}
```

`Runner`也可用于显示`ConditionEvaluationReport`. 报告可以在`INFO`或`DEBUG`水平打印。以下示例显示了如何使用`ConditionEvaluationReportLoggingListener`打印自动配置测试中的报告。

```
class MyConditionEvaluationReportingTests {
​
    @Test
    void autoConfigTest() {
        new ApplicationContextRunner()
            .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
            .run((context) -> {
                    // Test something...
            });
    }
​
}
```

**7.12.4.1.模拟 Web 上下文**

如果您需要测试仅在 servlet 或响应式 Web 应用程序上下文中运行的自动配置，请分别使用`WebApplicationContextRunner`或`ReactiveWebApplicationContextRunner`。

**7.12.4.2. 重写类路径**

还可以测试在运行时不存在特定类和/或包时会发生什么。Spring Boot 附带一个runner 可以轻松使用的`FilteredClassLoader`。在以下示例中，我们断言如果`MyService`不存在，则自动配置被正确禁用：

```
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
    this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
            .run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
```

#### 7.12.5.创建自己的启动器(starter)

一个典型的 Spring Boot 启动器包含自动配置和自定义给定技术的基础设施的代码，我们称之为“acme”。为了使其易于扩展，可以将专用命名空间中的许多配置键暴露给环境。最后，提供了一个“starter”依赖项来帮助用户尽可能轻松地开始。

具体来说，自定义启动器可以包含以下内容：

* 包含“acme”的自动配置代码的`autoconfigure`模块。
* 提供对`starter`模块的依赖关系的`autoconfigure`模块以及“acme”和通常有用的任何其他依赖关系。简而言之，添加启动器应该提供开始使用该库所需的一切。

两个模块中的这种分离绝不是必要的。如果“acme”有多种风格、选项或可选功能，那么最好将自动配置分开，因为您可以清楚地表达某些功能是可选的事实。此外，您还可以制作一个启动器来提供有关这些可选依赖项的意见。同时，其他人只能依靠`autoconfigure`模块，制作自己的不同意见的starter。

如果自动配置相对简单并且没有可选功能，那么在启动器中合并两个模块绝对是一种选择。

**7.12.5.1.命名**

您应该确保为您的启动器提供适当的命名空间。即使您使用不同的 Maven `groupId`，也不要使用以`spring-boot`开头的模块名称。以便我们将来可能会为您自动配置的内容提供官方支持。

根据经验，您应该在启动器之后命名组合模块。例如，假设您正在为“acme”创建一个启动器，并且您命名自动配置模块`acme-spring-boot`和启动器`acme-spring-boot-starter`。如果您只有一个模块将两者结合起来，请将其命名为`acme-spring-boot-starter`.

**7.12.5.2. 配置键(ConfigurationProperties)**

如果您的启动器提供配置键，请为它们使用唯一的命名空间。特别是，不要将您的键包含在 Spring Boot 使用的命名空间中（例如`server`、`management`、`spring`等）。如果您使用相同的命名空间，我们将来可能会以破坏您的模块的方式修改这些命名空间。根据经验，在所有键前面加上您自己的命名空间（例如`acme`）。

确保通过为每个属性添加字段 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 ...
​
}
```

您应该只使用带有`@ConfigurationProperties`字段的纯文本Javadoc ，因为它们在添加到 JSON 之前不会被处理。

以下是我们在内部遵循的一些规则，以确保描述一致：

* 不要以“The”或“A”开始描述。
* 对于`boolean`类型，以“Whether”或“Enable”开始描述。
* 对于基于集合的类型，以“Comma-separated list”开始描述
* 如果默认单位与毫秒不同，则使用`java.time.Duration`而不是`long`描述默认单位，例如“If a duration suffix is not specified, seconds will be used”。
* 除非必须在运行时确定，否则不要在描述中提供默认值。

确保[触发元数据生成](https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor)，以便您的密钥也可以使用 IDE 帮助。您可能需要查看生成的元数据 ( `META-INF/spring-configuration-metadata.json`) 以确保正确记录您的密钥。在兼容的 IDE 中使用您自己的启动器也是验证元数据质量的好主意。

**7.12.5.3.“自动配置”模块**

`autoconfigure`模块包含开始使用该库所需的一切。它还可能包含配置键定义（例如`@ConfigurationProperties`）和任何回调接口，可用于进一步自定义组件的初始化方式。

您应该将库的依赖项标记为可选，以便您可以更轻松地将`autoconfigure`模块包含在项目中。如果您这样做，则不会提供该库，并且默认情况下，Spring Boot 会退出。 Spring Boot 使用注解处理器来收集元数据文件 ( `META-INF/spring-autoconfigure-metadata.properties`) 中的自动配置条件。如果该文件存在，它将用于急切地过滤不匹配的自动配置，这将缩短启动时间。建议在包含自动配置的模块中添加以下依赖项：

```
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>
```

如果您在应用程序中直接定义了自动配置，请确保配置了`spring-boot-maven-plugin`以防止`repackage`目标将依赖项添加到 fat jar 中：

```
<project>
    <build>
        <plugins>
            <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>
        </plugins>
    </build>
</project>
```

对于 Gradle 4.5 及更早版本，应在`compileOnly`配置中声明依赖项，如以下示例所示：

```
dependencies {
    compileOnly "org.springframework.boot:spring-boot-autoconfigure-processor"
}
```

对于 Gradle 4.6 及更高版本，应在`annotationProcessor`配置中声明依赖项，如下例所示：

```
dependencies {
    annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}
```

**7.12.5.4.Starter模块**

启动器实际上是一个空罐子。它的唯一目的是提供必要的依赖项以使用该库。您可以将其视为对入门所需内容的固执己见。

不要对添加启动器的项目做出假设。如果您要自动配置的库通常需要其他启动器，请同时提及它们。如果可选依赖项的数量很高，则提供一组适当的*默认*依赖项可能会很困难，因为您应该避免包含对于库的典型使用而言不必要的依赖项。换句话说，您不应该包含可选依赖项。 无论哪种方式，您的 starter 都必须直接或间接引用核心 Spring Boot starter ( `spring-boot-starter`)（如果您的 starter 依赖于另一个 starter，则无需添加它）。如果仅使用您的自定义启动器创建项目，则 Spring Boot 的核心功能将因核心启动器的存在而受到尊重。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.shiker.tech/spring-boot-can-kao-wen-dang/7.-he-xin-te-xing/7.12.-chuang-jian-nin-zi-ji-de-zi-dong-pei-zhi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
