# hello，这里是翻译橙

#### 翻译橙

翻译橙是一个专注于存放翻译后的官方文档和其他书籍资料的网站。我们的目标是为用户提供高质量的翻译内容，帮助他们更好地理解和使用各种技术文档和书籍。

#### 主要功能

* **文档存储**：轻松快捷地访问各类翻译文档。
* **多语种支持**：提供多种语言的文档翻译，满足不同用户的需求。
* **定期更新**：不断添加新的翻译资源，保持资料的时效性。
* **用户反馈**：用户可以提交反馈和建议，帮助我们提升翻译质量。

我们致力于为广大的读者群体提供一个可靠的翻译文档平台，使信息的获取更为简单和高效。


# spring boot参考文档

Hello，本项目是对[springboot官方文档](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using)的中文翻译版，对应的gitbook地址为[翻译橙](https://doc.shiker.tech/)

如果你想参与到[springboot官方文档](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using)的翻译中，请到主站联系我，或者点击贡献项目提出issue并star！


# 1. 法律

版权所有 © 2012-2023

您可以制作本文档的副本供您自己使用或分发给其他人，前提是您不对此类副本收取任何费用，并且每份副本都包含本版权声明，无论是以印刷版还是电子版形式分发。


# 2. 寻求帮助

如果您在使用 Spring Boot 时遇到问题，我们很乐意提供帮助。

* 尝试操作[方法文档](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto)。他们提供最常见问题的解决方案。
* 学习 Spring 基础知识。Spring Boot 构建在许多其他 Spring 项目之上。检查[spring.io](https://spring.io/)网站以获取大量参考文档。如果您刚开始使用 Spring，请尝试其中一个[指南](https://spring.io/guides)。
* 问一个问题。我们监控[stackoverflow.com](https://stackoverflow.com/)是否有标记为[`spring-boot`](https://stackoverflow.com/tags/spring-boot) 的问题。
* 在[github.com/spring-projects/spring-boot/issues](https://github.com/spring-projects/spring-boot/issues)报告 Spring Boot 的错误。

> Spring Boot 的所有内容都是开源的，包括文档。如果您发现文档存在问题或者想要改进它们，请[参与其中](https://github.com/spring-projects/spring-boot/tree/v3.2.0)。


# 3. 文档概述

本节提供 Spring Boot 参考文档的简要概述。它充当文档其余部分的地图。

本文档的最新副本可在[docs.spring.io/spring-boot/docs/current/reference/](https://docs.spring.io/spring-boot/docs/current/reference/)获取。

#### 3.1. 第一步

如果您正在开始使用 Spring Boot 或“Spring”，请从[以下主题](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started)开始：

* **从头开始：** [概述](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.introducing-spring-boot)| [要求](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.system-requirements)| [安装](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.installing)
* **教程：** [第 1 部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.first-application)| [第2部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.first-application.code)
* **运行您的示例：** [第 1 部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.first-application.run)| [第2部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.first-application.executable-jar)

#### 3.2. 从早期版本升级

您应该始终确保正在运行[受支持](https://github.com/spring-projects/spring-boot/wiki/Supported-Versions)的 Spring Boot 版本。

根据您要升级到的版本，您可以在此处找到一些其他提示：

* **从 1.x：** [从 1.x 升级](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#upgrading.from-1x)
* **到新功能版本：** [升级到新功能版本](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#upgrading.to-feature)
* **Spring Boot CLI：** [升级 Spring Boot CLI](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#upgrading.cli)

#### 3.3. 使用 Spring Boot 进行开发

准备好真正开始使用 Spring Boot 了吗？[我们为您服务](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using)：

* **构建系统：** [Maven](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.maven) | [Gradle](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.gradle)| [Ant](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.ant)| [Starters](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.starters)
* **最佳实践：** [代码结构](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.structuring-your-code)| [@配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.configuration-classes)| [@EnableAutoConfiguration](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration) | [Bean 和依赖注入](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.spring-beans-and-dependency-injection)
* **运行您的代码：** [IDE](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.running-your-application.from-an-ide) | [包装](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.running-your-application.as-a-packaged-application)| [Maven](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.running-your-application.with-the-maven-plugin) | [gradle](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.running-your-application.with-the-gradle-plugin)
* **打包您的应用程序：** [生产 jar](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.packaging-for-production)
* **Spring Boot CLI：** [使用 CLI](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#cli)

#### 3.4. 了解 Spring Boot 功能

需要有关 Spring Boot 核心功能的更多详细信息？ [以下内容适合您](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features)：

* **弹簧应用程序：** [SpringApplication](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application)
* **外部配置：** [外部配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config)
* **Profiles：** [Profiles](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.profiles)
* **日志记录：** [日志记录](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging)

#### 3.5. 网络

如果您开发Spring Boot Web应用程序，请看一下以下内容：

* **Servlet Web 应用程序：** [Spring MVC、Jersey、嵌入式 Servlet 容器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet)
* **反应式 Web 应用程序：** [Spring Webflux、嵌入式 Servlet 容器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.reactive)
* **优雅关机：** [优雅关机](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.graceful-shutdown)
* **Spring Security：** [默认安全配置、OAuth2、SAML 自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.security)
* **Spring Session：** [Spring Session的自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.spring-session)
* **Spring HATEOAS：** [Spring HATEOAS 自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.spring-hateoas)

#### 3.6. 数据

如果您的应用程序处理数据存储，您可以在此处查看如何配置它：

* **SQL：** [配置 SQL 数据存储、嵌入式数据库支持、连接池等。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.sql)
* **NOSQL：** [自动配置 NOSQL 存储，例如 Redis、MongoDB、Neo4j 等。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.nosql)

#### 3.7. 消息传递

如果您的应用程序使用任何消息传递协议，请参阅以下一个或多个部分：

* **JMS：** [ActiveMQ和Artemis的自动配置，通过JMS发送和接收消息](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.jms)
* **AMQP：** [RabbitMQ 自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.amqp)
* **Kafka：** [Spring Kafka 自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.kafka)
* **Pulsar：** [Spring for Apache Pulsar 的自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.pulsar)
* **RSocket：** [Spring 框架 RSocket 支持的自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.rsocket)
* **Spring Integration：** [Spring Integration 的自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.spring-integration)

#### 3.8. IO

如果您的应用程序需要 IO 功能，请参阅以下一个或多个部分：

* **缓存：** [EhCache、Hazelcast、Infinispan 等的缓存支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching)
* **Quartz：** [Quartz调度](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.quartz)
* **邮件：** [发送电子邮件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.email)
* **验证：** [JSR-303 验证](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.validation)
* **REST 客户端：** [使用 RestTemplate 和 WebClient 调用 REST 服务](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.rest-client)
* **Webservices：** [Spring Web 服务的自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.webservices)
* **JTA：** [使用 JTA 进行分布式事务](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.jta)

#### 3.9. 容器镜像

Spring Boot 为构建高效的容器镜像提供一流的支持。你可以在这里读更多关于它的内容：

* **高效容器镜像：** [优化 Docker 镜像等容器镜像的技巧](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images.efficient-images)
* **Dockerfiles：** [使用 dockerfiles 构建容器镜像](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images.dockerfiles)
* **云原生构建包：** [使用 Maven 和 Gradle 支持云原生构建包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images.buildpacks)

#### 3.10. 转向生产

当您准备好将 Spring Boot 应用程序推向生产环境时，我们有[一些](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator)您可能会喜欢的技巧：

* **管理端点：** [概述](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.endpoints)
* **连接选项：** [HTTP](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.monitoring) | [JMX](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.jmx)
* **监控：** [指标](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.metrics)| [审计](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.auditing)| [HTTP 交换](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.http-exchanges)| [过程](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.process-monitoring)

#### 3.11. GraalVM 本机镜像

Spring Boot 应用程序可以使用 GraalVM 转换为本机可执行文件。您可以在此处阅读有关我们的本机图像支持的更多信息：

* **GraalVM 本机映像：** [简介](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.introducing-graalvm-native-images)| [与 JVM 的主要区别](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments)| [提前处理](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.introducing-graalvm-native-images.understanding-aot-processing)
* **入门：** [构建包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.developing-your-first-application.buildpacks)| [本机构建工具](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.developing-your-first-application.native-build-tools)
* **测试：** [JVM](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.testing.with-the-jvm) | [本机构建工具](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.testing.with-native-build-tools)
* **高级主题：** [嵌套配置属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.advanced.nested-configuration-properties)| [转换 JAR](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.advanced.converting-executable-jars) | [已知限制](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.advanced.known-limitations)

#### 3.12. 高级主题

最后，我们为更高级的用户提供了一些主题：

* **Spring Boot 应用程序部署：** [云部署](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#deployment.cloud)| [操作系统服务](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#deployment.installing)
* **构建工具插件：** [Maven](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#build-tool-plugins.maven) | [Gradle](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#build-tool-plugins.gradle)
* **附录：** [应用程序属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.application-properties)| [配置元数据](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.configuration-metadata)| [自动配置类](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.auto-configuration-classes)| [测试自动配置注释](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.test-auto-configuration)| [可执行jar包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.executable-jar)| [依赖版本](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.dependency-versions)


# 4. 入门

如果您正在开始使用 Spring Boot 或一般的“Spring”，请先阅读本节。它回答了基本的“什么？”、“如何？” 和“为什么？” 问题。它包括 Spring Boot 的介绍以及安装说明。然后，我们将引导您构建您的第一个 Spring Boot 应用程序，并讨论一些核心原则。

#### 4.1. Spring Boot 简介

Spring Boot 可帮助您创建可以运行的独立的、生产级的、基于 Spring 的应用程序。我们对 Spring 平台和第三方库采取固执己见的观点，以便您可以轻松上手。大多数 Spring Boot 应用程序只需要很少的 Spring 配置。

您可以使用 Spring Boot 创建可以通过使用`java -jar`或更传统的 war 部署来启动的 Java 应用程序。

我们的主要目标是：

* 为所有 Spring 开发提供更快、更广泛的入门体验。
* 开箱即用，但当需求开始偏离默认值时，要迅速摆脱困境。
* 提供一系列大型项目通用的非功能特性（例如嵌入式服务器、安全性、指标、运行状况检查和外部化配置）。
* 绝对没有代码生成（当不针对本机图像时）并且不需要 XML 配置。

#### 4.2. 系统要求

Spring Boot 3.2.0 需要[Java 17](https://www.java.com/)，并且兼容 Java 21（包括 Java 21）。 还需要[Spring Framework 6.1.1或更高版本。](https://docs.spring.io/spring-framework/reference/6.1/)

为以下构建工具提供显式构建支持：

| 构建工具   | 版本                  |
| ------ | ------------------- |
| Maven  | 3.6.3 或更高版本         |
| Gradle | 7.x（7.5 或更高版本）和 8.x |

**4.2.1. Servlet 容器**

Spring Boot 支持以下嵌入式 servlet 容器：

| 姓名           | 小服务程序版本 |
| ------------ | ------- |
| Tomcat 10.1  | 6.0     |
| Jetty 12.0   | 6.0     |
| Undertow 2.3 | 6.0     |

您还可以将 Spring Boot 应用程序部署到任何 servlet 5.0+ 兼容容器。

**4.2.2. GraalVM 本机镜像**

Spring Boot 应用程序可以使用 GraalVM 22.3 或更高版本转换为 [Native Image](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.introducing-graalvm-native-images)。

可以使用[本机构建工具](https://github.com/graalvm/native-build-tools)Gradle/Maven 插件或GraalVM 提供的`native-image`工具来创建映像。[您还可以使用本机映像 Paketo buildpack](https://github.com/paketo-buildpacks/native-image)创建本机映像。

支持以下版本：

| 姓名                         | 版本     |
| -------------------------- | ------ |
| GraalVM Community          | 22.3   |
| 本机构建工具（Native Build Tools） | 0.9.28 |

#### 4.3. 安装 Spring Boot

Spring Boot 可以与“经典”Java 开发工具一起使用，也可以作为命令行工具安装。无论哪种方式，您都需要[Java SDK v17](https://www.java.com/)或更高版本。在开始之前，您应该使用以下命令检查当前的 Java 安装：

```
$ java -version
```

如果您是 Java 开发新手或者想要尝试 Spring Boot，您可能需要首先尝试[Spring Boot CLI](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.installing.cli)（命令行界面）。否则，请继续阅读“经典”安装说明。

**4.3.1. Java 开发人员安装说明**

您可以像使用任何标准 Java 库一样使用 Spring Boot。为此，请在类路径中包含适当的`spring-boot-*.jar`文件。Spring Boot 不需要集成任何特殊工具，因此您可以使用任何 IDE 或文本编辑器。此外，Spring Boot 应用程序没有什么特别之处，因此您可以像运行任何其他 Java 程序一样运行和调试 Spring Boot 应用程序。

尽管您*可以*复制 Spring Boot jar，但我们通常建议您使用支持依赖项管理的构建工具（例如 Maven 或 Gradle）。

**Maven安装**

Spring Boot 与 Apache Maven 3.6.3 或更高版本兼容。如果您尚未安装 Maven，可以按照[maven.apache.org](https://maven.apache.org/)上的说明进行操作。

> 在许多操作系统上，Maven 可以通过包管理器安装。如果您使用 OSX Homebrew，请尝试`brew install maven`. Ubuntu 用户可以运行`sudo apt-get install maven`. [使用Chocolatey](https://chocolatey.org/)的 Windows 用户可以`choco install maven`从提升的（管理员）提示符下运行。

Spring Boot 依赖项使用组 ID`org.springframework.boot`。通常，您的 Maven POM 文件继承自`spring-boot-starter-parent`项目并声明对一个或多个[“Starters”](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.starters)的依赖关系。Spring Boot 还提供了一个可选的[Maven 插件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#build-tool-plugins.maven)来创建可执行 jar。

有关 Spring Boot 和 Maven 入门的更多详细信息，请参阅Maven 插件参考指南的[入门部分。](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/htmlsingle/#getting-started)

**Gradle安装**

Spring Boot 与 Gradle 7.x（7.5 或更高版本）和 8.x 兼容。如果您尚未安装 Gradle，您可以按照[gradle.org](https://gradle.org/)上的说明进行操作。

Spring Boot 依赖项可以使用`org.springframework.boot` `group`. 通常，您的项目会声明对一个或多个[“Starters”](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.starters)的依赖关系。Spring Boot 提供了一个有用的[Gradle 插件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#build-tool-plugins.gradle)，可用于简化依赖项声明并创建可执行 jar。

Gradle 包装器

当您需要构建项目时，Gradle Wrapper 提供了一种“获取”Gradle 的好方法。它是一个小脚本和库，您可以将其与代码一起提交以引导构建过程。有关详细信息，请参阅[docs.gradle.org/current/userguide/gradle\_wrapper.html](https://docs.gradle.org/current/userguide/gradle_wrapper.html)。

有关 Spring Boot 和 Gradle 入门的更多详细信息，请参阅Gradle 插件参考指南的[入门部分。](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/htmlsingle/#getting-started)

**4.3.2. 安装 Spring Boot CLI**

Spring Boot CLI（命令行界面）是一个命令行工具，可用于使用 Spring 快速构建原型。

您不需要使用 CLI 来使用 Spring Boot，但它是无需 IDE 即可启动 Spring 应用程序的快速方法。

**手动安装**

您可以从以下位置之一下载 Spring CLI 发行版：

* [spring-boot-cli-3.2.0-bin.zip](https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-cli/3.2.0/spring-boot-cli-3.2.0-bin.zip)
* [spring-boot-cli-3.2.0-bin.tar.gz](https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-cli/3.2.0/spring-boot-cli-3.2.0-bin.tar.gz)

下载后，按照解压存档中的[INSTALL.txt说明进行操作。](https://raw.githubusercontent.com/spring-projects/spring-boot/v3.2.0/spring-boot-project/spring-boot-tools/spring-boot-cli/src/main/content/INSTALL.txt)总之，`.zip`文件的 `bin/` 目录中有一个 spring 脚本（Windows 下为 spring.bat）。或者，您可以将`java -jar`与`.jar`文件一起使用（该脚本可帮助您确保类路径设置正确）。

**用SDKMAN安装！**

SDKMAN！（软件开发工具包管理器）可用于管理各种二进制 SDK 的多个版本，包括 Groovy 和 Spring Boot CLI。从[sdkman.io](https://sdkman.io/)获取 SDKMAN！并使用以下命令安装 Spring Boot：

```
$ sdk install springboot
$ spring --version
Spring CLI v3.2.0
```

如果您为 CLI 开发功能并希望访问您构建的版本，请使用以下命令：

```
$ sdk install springboot dev /path/to/spring-boot/spring-boot-cli/target/spring-boot-cli-3.2.0-bin/spring-3.2.0/
$ sdk default springboot dev
$ spring --version
Spring CLI v3.2.0
```

前面的说明安装了一个名为 `dev` 实例的`spring` 本地实例。它指向您的目标构建位置，因此每次您重建 Spring Boot 时，`spring` 都是最新的。

您可以通过运行以下命令来查看它：

```
$ sdk ls springboot
​
================================================================================
Available Springboot Versions
================================================================================
> + dev
* 3.2.0
​
================================================================================
+ - local version
* - installed
> - currently in use
================================================================================
```

**OSX 自制软件安装**

如果您使用的是 Mac 并使用[Homebrew](https://brew.sh/)，则可以使用以下命令安装 Spring Boot CLI：

```
$ brew tap spring-io/tap
$ brew install spring-boot
```

Homebrew 会安装`spring`到`/usr/local/bin`.

> 如果您没有看到该公式，则您安装的brew 可能已过时。在这种情况下，请运行`brew update`并重试。

**MacPorts安装**

如果您使用的是 Mac 并使用[MacPorts](https://www.macports.org/)，则可以使用以下命令安装 Spring Boot CLI：

```
$ sudo port install spring-boot-cli
```

**命令行完成**

Spring Boot CLI 也包含为[BASH](https://en.wikipedia.org/wiki/Bash_\(Unix_shell\))和[zsh](https://en.wikipedia.org/wiki/Z_shell) shell 提供命令完成的脚本。您可以在任何 shell 中获取该脚本（也称为`spring`），或者将其放入您的个人或系统范围的 bash 完成初始化中。，或者将其放入您的个人或系统范围的 bash 完成初始化中。在 Debian 系统上，系统范围的脚本位于其中，`<installation location>/shell-completion/bash`并且在新 shell 启动时执行该目录中的所有脚本。例如，如果您已使用 SDKMAN! 安装，则要手动运行脚本，请使用以下命令：

```
$ . ~/.sdkman/candidates/springboot/current/shell-completion/bash/spring
$ spring <HIT TAB HERE>
  grab  help  jar  run  test  version
```

> 如果您使用 Homebrew 或 MacPorts 安装 Spring Boot CLI，命令行完成脚本将自动注册到您的 shell。

**Windows Scoop 安装**

如果您在 Windows 上并使用[Scoop](https://scoop.sh/)，则可以使用以下命令安装 Spring Boot CLI：

```
> scoop bucket add extras
> scoop install springboot
```

Scoop 安装`spring`到`~/scoop/apps/springboot/current/bin`.

> 如果您没有看到应用程序清单，则您安装的 scoop 可能已过时。在这种情况下，请运行`scoop update`并重试。

#### 4.4. 开发您的第一个 Spring Boot 应用程序

本节介绍如何开发一个小型的“Hello World！” Web 应用程序，突出显示了 Spring Boot 的一些关键功能。您可以选择 Maven 或 Gradle 作为构建系统。

> spring.io网站包含许多使用 Spring Boot 的[“](https://spring.io/)入门”[指南。](https://spring.io/guides)如果您需要解决特定问题，请先检查那里。[您可以通过转至start.spring.io](https://start.spring.io/)并从依赖项搜索器中选择“Web”启动器来快捷执行以下步骤。这样做会生成一个新的项目结构，以便您可以[立即开始编码](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.first-application.code)。查看[start.spring.io 用户指南](https://github.com/spring-io/start.spring.io/blob/main/USING.adoc)了解更多详细信息。

**4.4.1. 先决条件**

在开始之前，打开终端并运行以下命令以确保您安装了有效版本的 Java：

```
$ java -version
openjdk version "17.0.4.1" 2022-08-12 LTS
OpenJDK Runtime Environment (build 17.0.4.1+1-LTS)
OpenJDK 64-Bit Server VM (build 17.0.4.1+1-LTS, mixed mode, sharing)
```

> 该示例需要在其自己的目录中创建。后续说明假定您已创建合适的目录并且它是您的当前目录。

**Maven**

如果要使用 Maven，请确保已安装 Maven：

```
$ mvn -v
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: usr/Users/developer/tools/maven/3.8.5
Java version: 17.0.4.1, vendor: BellSoft, runtime: /Users/developer/sdkman/candidates/java/17.0.4.1-librca
```

**Gradle**

如果您想使用 Gradle，请确保已安装 Gradle：

```
$ gradle --version
​
------------------------------------------------------------
Gradle 8.1.1
------------------------------------------------------------
​
Build time:   2023-04-21 12:31:26 UTC
Revision:     1cf537a851c635c364a4214885f8b9798051175b
​
Kotlin:       1.8.10
Groovy:       3.0.15
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.7 (BellSoft 17.0.7+7-LTS)
OS:           Linux 6.2.12-200.fc37.aarch64 aarch64
```

**4.4.2. 使用 Maven 设置项目**

我们需要首先创建一个 Maven`pom.xml`文件。这`pom.xml`是用于构建项目的配方。打开您最喜欢的文本编辑器并添加以下内容：

```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
​
    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>
​
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>
​
    <!-- Additional lines to be added here... -->
​
</project>
```

前面的清单应该为您提供一个工作版本。您可以通过运行来测试它`mvn package`（目前，您可以忽略“jar将为空 - 没有内容被标记为包含！”警告）。

> 此时，您可以将项目导入 IDE（大多数现代 Java IDE 都包含对 Maven 的内置支持）。为简单起见，我们在此示例中继续使用纯文本编辑器。

**4.4.3. 使用 Gradle 设置项目**

我们需要首先创建一个 Gradle`build.gradle`文件。这`build.gradle`是用于构建项目的构建脚本。打开您最喜欢的文本编辑器并添加以下内容：

```
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
}
​
apply plugin: 'io.spring.dependency-management'
​
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
​
repositories {
    mavenCentral()
}
​
dependencies {
}
```

前面的清单应该为您提供一个工作版本。您可以通过运行来测试它`gradle classes`。

> 此时，您可以将项目导入 IDE（大多数现代 Java IDE 都包含对 Gradle 的内置支持）。为简单起见，我们在此示例中继续使用纯文本编辑器。

**4.4.4. 添加类路径依赖项**

Spring Boot 提供了许多“Starters”，可让您将 jar 添加到类路径中。“Starters”提供了开发特定类型的应用程序时可能需要的依赖项。

**Maven**

大多数 Spring Boot 应用程序在POM的`parent` 部分中使用`spring-boot-starter-parent`。`spring-boot-starter-parent`是一个特殊的启动器，提供有用的 Maven 默认值。它还提供了一个[`dependency-management`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.dependency-management)部分，以便您可以省略依赖项的`version`标签。

由于我们正在开发一个 Web 应用程序，因此我们添加了一个`spring-boot-starter-web`依赖项。在此之前，我们可以通过运行以下命令来查看当前拥有的内容：

```
$ mvn dependency:tree
​
[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT
```

`mvn dependency:tree`命令打印项目依赖项的树表示。您可以看到`spring-boot-starter-parent`本身不提供依赖项。要添加必要的依赖项，请编辑`pom.xml`并添加`spring-boot-starter-web`紧邻该部分下方的依赖项`parent`：

```
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
```

如果再次运行`mvn dependency:tree`，您会发现现在有许多额外的依赖项，包括 Tomcat Web 服务器和 Spring Boot 本身。

**Gradle**

大多数 Spring Boot 应用程序都使用`org.springframework.boot` Gradle 插件。该插件提供有用的默认值和 Gradle 任务。`io.spring.dependency-management`Gradle插件提供[依赖项管理](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.dependency-management)，以便您可以省略依赖项的`version`标签。

由于我们正在开发一个 Web 应用程序，因此我们添加了一个`spring-boot-starter-web`依赖项。在此之前，我们可以通过运行以下命令来查看当前拥有的内容：

```
$ gradle dependencies
​
> Task :dependencies
​
------------------------------------------------------------
Root project 'myproject'
------------------------------------------------------------
```

`gradle dependencies`命令打印项目依赖项的树表示。目前，该项目没有依赖项。要添加必要的依赖项，请编辑您`build.gradle`的依赖项并在`dependencies`部分中添加`spring-boot-starter-web`依赖项：

```
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}
```

如果再次运行`gradle dependencies`，您会发现现在有许多额外的依赖项，包括 Tomcat Web 服务器和 Spring Boot 本身。

**4.4.5. 编写代码**

为了完成我们的应用程序，我们需要创建一个 Java 文件。默认情况下，Maven 和 Gradle 从`src/main/java`编译源代码，因此您需要创建该目录结构，然后添加一个名为`src/main/java/MyApplication.java`包含以下代码的文件：

```
package com.example;
​
@RestController
@SpringBootApplication
public class MyApplication {
​
    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }
​
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
​
}
```

虽然这里没有太多代码，但已经做了很多事情。我们将在接下来的几节中逐步介绍重要部分。

**@RestController 和 @RequestMapping 注解**

我们类的第一个注释`MyApplication`是`@RestController`. 这称为*构造型*注释。它为阅读代码的人提供提示，并为 Spring 提供该类扮演特定角色的提示。在本例中，我们的类是一个 web `@Controller`，因此 Spring 在处理传入的 Web 请求时会考虑它。

`@RequestMapping`注释提供“路由”信息。它告诉 Spring 任何具有该`/`路径的 HTTP 请求都应该映射到`home`方法。`@RestController`注释告诉 Spring 将结果字符串直接渲染回调用者。

> `@RestController`和`@RequestMapping`注释是 Spring MVC 注释（它们不特定于 Spring Boot）。有关更多详细信息，请参阅Spring 参考文档中的 [MVC 部分。](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc.html)

**@SpringBootApplication 注解**

第二个类级注释是`@SpringBootApplication`。该注释称为*元注释*，它结合了`@SpringBootConfiguration`,`@EnableAutoConfiguration`和`@ComponentScan`。

其中，我们最感兴趣的注释是`@EnableAutoConfiguration`. `@EnableAutoConfiguration`告诉 Spring Boot 根据您添加的 jar 依赖项“猜测”您想要如何配置 Spring。由于`spring-boot-starter-web`添加了 Tomcat 和 Spring MVC，自动配置假定您正在开发 Web 应用程序并相应地设置 Spring。

> **启动器和自动配置**
>
> 自动配置旨在与“初学者”很好地配合，但这两个概念并不直接相关。您可以自由选择启动程序之外的 jar 依赖项。Spring Boot 仍然尽力自动配置您的应用程序。

**“main”方法**

我们应用程序的最后一部分是`main`方法。这是遵循 Java 应用程序入口点约定的标准方法。我们的 main 方法通过调用`run`委托给 Spring Boot 的`SpringApplication`类。 `SpringApplication`引导我们的应用程序，启动 Spring，进而启动自动配置的 Tomcat Web 服务器。我们需要将`MyApplication.class`作为参数传递给该`run`方法来让`SpringApplication`判断哪个是主要的 Spring 组件。`args`数组也会被传递以公开任何命令行参数。

**4.4.6. 运行示例**

**Maven**

此时，您的应用程序应该可以运行。由于您使用了`spring-boot-starter-parent`POM，您就有了一个可以用来启动应用程序的有用`run`目标。从根项目目录键入`mvn spring-boot:run`以启动应用程序。您应该看到类似于以下内容的输出：

```
$ mvn spring-boot:run
​
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.2.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.906 seconds (process running for 6.514)
```

如果您打开 Web 浏览器`localhost:8080`，您应该看到以下输出：

```
你好世界！
```

要正常退出应用程序，请按`ctrl-c`。

**Gradle**

此时，您的应用程序应该可以运行。由于您使用了`org.springframework.boot`Gradle 插件，您就有了一个可以用来启动应用程序的有用`bootRun`目标。从根项目目录键入`gradle bootRun`以启动应用程序。您应该看到类似于以下内容的输出：

```
$ gradle bootRun
​
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.2.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.906 seconds (process running for 6.514)
```

如果您打开 Web 浏览器`localhost:8080`，您应该看到以下输出：

```
你好世界！
```

要正常退出应用程序，请按`ctrl-c`。

**4.4.7. 创建可执行 Jar**

我们通过创建一个可以在生产中运行的完全独立的可执行 jar 文件来完成我们的示例。可执行 jar（有时称为“uber jar”或“fat jar”）是包含已编译类以及代码运行所需的所有 jar 依赖项的档案。

> **可执行 jar 和 Java**
>
> Java 不提供加载嵌套 jar 文件（本身包含在 jar 中的 jar 文件）的标准方法。如果您希望分发独立的应用程序，这可能会出现问题。
>
> 为了解决这个问题，许多开发人员使用“uber”jar。uber jar 将所有应用程序依赖项中的所有类打包到一个存档中。这种方法的问题是很难看出应用程序中有哪些库。如果在多个 jar 中使用相同的文件名（但内容不同），也可能会出现问题。
>
> Spring Boot 采用了[不同的方法](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.executable-jar)，让您可以直接嵌套 jar。

**`gradle bootRun`**

要创建可执行 jar，我们需要将其添加`spring-boot-maven-plugin`到我们的`pom.xml`. 为此，请在该`dependencies`部分下方插入以下行：

```
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
```

`spring-boot-starter-parent`POM包含绑定`repackage`目标的`<executions>`配置。如果不使用父POM，则需要自己声明此配置。有关详细信息，请参阅[插件文档](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/htmlsingle/#getting-started)。

保存`pom.xml`并从命令行运行`mvn package`，如下所示：

```
$ mvn package
​
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] .... ..
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ myproject ---
[INFO] Building jar: /Users/developer/example/spring-boot-example/target/myproject-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:3.2.0:repackage (default) @ myproject ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
```

如果您查看目录`target`，您应该会看到`myproject-0.0.1-SNAPSHOT.jar`. 文件大小应约为 18 MB。如果你想查看内部，可以使用`jar tvf`，如下所示：

```
$ jar tvf target/myproject-0.0.1-SNAPSHOT.jar
```

您还应该在`target`目录中看到一个更小的文件`myproject-0.0.1-SNAPSHOT.jar.original`。这是 Maven 在被 Spring Boot 重新打包之前创建的原始 jar 文件。

要运行该应用程序，请使用`java -jar`以下命令：

```
$ java -jar target/myproject-0.0.1-SNAPSHOT.jar
​
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.2.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.999 seconds (process running for 1.253)
```

和以前一样，要退出应用程序，请按`ctrl-c`。

**Gradle**

要创建可执行 jar，我们需要从命令行运行`gradle bootJar`，如下所示：

```
$ gradle bootJar
​
BUILD SUCCESSFUL in 639ms
3 actionable tasks: 3 executed
```

如果您查看`build/libs`目录，您应该会看到`myproject-0.0.1-SNAPSHOT.jar`. 文件大小应约为 18 MB。如果你想查看内部，可以使用`jar tvf`，如下所示：

```
$ jar tvf build/libs/myproject-0.0.1-SNAPSHOT.jar
```

要运行该应用程序，请使用`java -jar`以下命令：

```
$ java -jar build/libs/myproject-0.0.1-SNAPSHOT.jar
​
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.2.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.999 seconds (process running for 1.253)
```

和以前一样，要退出应用程序，请按`ctrl-c`。

#### 4.5. 接下来读什么

希望本节提供了一些 Spring Boot 基础知识，并帮助您编写自己的应用程序。如果您是面向任务的开发人员，您可能想跳到[spring.io](https://spring.io/)并遵循一些[入门](https://spring.io/guides/)指南来解决特定的“如何使用 Spring 做到这一点？” 问题。我们还有 Spring Boot 特定的“[操作方法](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto)”参考文档。

否则，下一个逻辑步骤是阅读[*使用 Spring Boot 进行开发*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using)。如果你真的不耐烦，你也可以直接阅读[*Spring Boot 特性*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features)。

<br>


# 5. 升级Spring Boot

[项目wiki](https://github.com/spring-projects/spring-boot/wiki)上提供了如何从早期版本的 Spring Boot 升级的说明。[按照发行说明](https://github.com/spring-projects/spring-boot/wiki#release-notes)部分中的链接查找您要升级到的版本。

升级说明始终是发行说明中的第一项。如果您落后了多个版本，请确保您还查看了所跳转版本的发行说明。

#### 5.1. 从 1.x 升级

如果您要从Spring Boot `1.x`版本升级，请查看[项目 wiki 上的“迁移指南”](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide)，其中提供了详细的升级说明。另请查看[“发行说明”，](https://github.com/spring-projects/spring-boot/wiki)了解每个版本的“新的和值得注意的”功能列表。

#### 5.2. 升级到新功能版本

升级到新功能版本时，某些属性可能已被重命名或删除。Spring Boot 提供了一种在启动时分析应用程序环境并打印诊断信息的方法，而且还可以在运行时为您临时迁移属性。要启用该功能，请将以下依赖项添加到您的项目中：

```
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-properties-migrator</artifactId>
    <scope>runtime</scope>
</dependency>
```

> 稍后添加到环境中的属性（例如使用`@PropertySource` 时）将不会被考虑在内。
>
> 完成迁移后，请确保从项目的依赖项中删除此模块。

#### 5.3. 升级 Spring Boot CLI

要升级现有的 CLI 安装，请使用适当的包管理器命令（例如，`brew upgrade`）。如果您手动安装了 CLI，请按照[标准说明](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.installing.cli.manual-installation)进行操作，并记住更新环境变量`PATH`以删除任何旧的引用。

#### 5.4. 接下来读什么

一旦您决定升级您的应用程序，您可以在文档的其余部分找到有关特定功能的详细信息。

Spring Boot 的文档特定于该版本，因此您在此处找到的任何信息都将包含该版本中的最新更改。


# 6. 使用 Spring Boot 进行开发

本节将更详细地介绍如何使用 Spring Boot。它涵盖了构建系统、自动配置以及如何运行应用程序等主题。我们还介绍了一些 Spring Boot 最佳实践。尽管 Spring Boot 没有什么特别之处（它只是您可以使用的另一个库），但有一些建议，如果遵循这些建议，将使您的开发过程变得更容易一些。

如果您开始使用 Spring Boot，您可能应该在深入本节之前阅读[*入门指南。*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started)


# 6.1. 构建系统

[*强烈建议您选择支持依赖关系管理*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.dependency-management)并且可以使用发布到“Maven Central”存储库的工件的构建系统。我们建议您选择 Maven 或 Gradle。Spring Boot 可以与其他构建系统（例如 Ant）一起使用，但它们并没有得到很好的支持。

**6.1.1. 依赖管理**

Spring Boot 的每个版本都提供了它支持的依赖项的精选列表。实际上，您不需要在构建配置中为任何这些依赖项提供版本，因为 Spring Boot 会为您管理它。当您升级 Spring Boot 本身时，这些依赖项也会以一致的方式升级。

> 如果需要，您仍然可以指定版本并覆盖 Spring Boot 的建议。

精选列表包含可与 Spring Boot 一起使用的所有 Spring 模块以及第三方库的精炼列表。该列表作为标准物料清单 (`spring-boot-dependencies`) 提供，可与[Maven](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.maven)和[Gradle](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.build-systems.gradle)一起使用。

> Spring Boot 的每个版本都与 Spring 框架的基本版本相关联。我们**强烈**建议您不要指定其版本。

**6.1.2. Maven**

要了解如何将 Spring Boot 与 Maven 结合使用，请参阅 Spring Boot 的 Maven 插件的文档：

* 参考（[HTML](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/htmlsingle/)和[PDF](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/pdf/spring-boot-maven-plugin-reference.pdf)）
* [应用程序编程接口](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/api/)

**6.1.3. Gradle**

要了解如何将 Spring Boot 与 Gradle 结合使用，请参阅 Spring Boot 的 Gradle 插件的文档：

* 参考（[HTML](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/htmlsingle/)和[PDF](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/pdf/spring-boot-gradle-plugin-reference.pdf)）
* [应用程序编程接口](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/api/)

**6.1.4. Ant**

可以使用 Apache Ant+Ivy 构建 Spring Boot 项目。“AntLib”模块`spring-boot-antlib`也可用于帮助 Ant 创建可执行 jar。

要声明依赖项，典型的`ivy.xml`文件类似于以下示例：

```
<ivy-module version="2.0">
    <info organisation="org.springframework.boot" module="spring-boot-sample-ant" />
    <configurations>
        <conf name="compile" description="everything needed to compile this module" />
        <conf name="runtime" extends="compile" description="everything needed to run this module" />
    </configurations>
    <dependencies>
        <dependency org="org.springframework.boot" name="spring-boot-starter"
            rev="${spring-boot.version}" conf="compile" />
    </dependencies>
</ivy-module>
```

典型的`build.xml`示例如下所示：

```
<project
    xmlns:ivy="antlib:org.apache.ivy.ant"
    xmlns:spring-boot="antlib:org.springframework.boot.ant"
    name="myapp" default="build">
​
    <property name="spring-boot.version" value="3.2.0" />
​
    <target name="resolve" description="--> retrieve dependencies with ivy">
        <ivy:retrieve pattern="lib/[conf]/[artifact]-[type]-[revision].[ext]" />
    </target>
​
    <target name="classpaths" depends="resolve">
        <path id="compile.classpath">
            <fileset dir="lib/compile" includes="*.jar" />
        </path>
    </target>
​
    <target name="init" depends="classpaths">
        <mkdir dir="build/classes" />
    </target>
​
    <target name="compile" depends="init" description="compile">
        <javac srcdir="src/main/java" destdir="build/classes" classpathref="compile.classpath" />
    </target>
​
    <target name="build" depends="compile">
        <spring-boot:exejar destfile="build/myapp.jar" classes="build/classes">
            <spring-boot:lib>
                <fileset dir="lib/runtime" />
            </spring-boot:lib>
        </spring-boot:exejar>
    </target>
</project>
```

如果您不想使用该`spring-boot-antlib`模块，请参阅[*在不使用 spring-boot-antlib 的情况下从 Ant 构建可执行存档*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.build.build-an-executable-archive-with-ant-without-using-spring-boot-antlib)“操作方法”。

**6.1.5. 初学者**

Starters 是一组方便的依赖描述符，您可以将其包含在应用程序中。您可以获得所需的所有 Spring 和相关技术的一站式商店，而无需寻找示例代码和复制粘贴依赖描述符负载。例如，如果您想开始使用 Spring 和 JPA 进行数据库访问，请`spring-boot-starter-data-jpa`在项目中包含依赖项。

启动器包含许多使项目快速启动和运行所需的依赖项，并具有一致的、受支持的托管传递依赖项集。

名字里有什么

所有**正式**启动器都遵循类似的命名模式；`spring-boot-starter-*`，其中`*`是特定类型的应用程序。这种命名结构旨在帮助您找到入门者。许多 IDE 中的 Maven 集成允许您按名称搜索依赖项。例如，安装了适当的 Eclipse 或 Spring Tools 插件后，您可以`ctrl-space`在 POM 编辑器中按 并键入“spring-boot-starter”以获得完整列表。

[正如“创建您自己的启动器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.developing-auto-configuration.custom-starter)”部分中所述，第三方启动器不应以 `spring-boot`开头，因为它是为官方 Spring Boot 工件保留的。相反，第三方启动器通常以项目名称开头。例如，名为`thirdpartyproject` 的第三方启动项目通常会命名为`thirdpartyproject-spring-boot-starter`。

Spring Boot 在`org.springframework.boot`组下提供了以下应用程序启动器：

| 姓名                                                | 描述                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spring-boot-starter`                             | 核心启动器，包括自动配置支持、日志记录和 YAML                                                                                                                                                                                                                                                                               |
| `spring-boot-starter-activemq`                    | 使用 Apache ActiveMQ 的 JMS 消息传递入门                                                                                                                                                                                                                                                                         |
| `spring-boot-starter-amqp`                        | 使用 Spring AMQP 和 Rabbit MQ 的入门程序                                                                                                                                                                                                                                                                        |
| `spring-boot-starter-aop`                         | 使用 Spring AOP 和 AspectJ 进行面向方面编程的入门程序                                                                                                                                                                                                                                                                   |
| `spring-boot-starter-artemis`                     | 使用 Apache Artemis 的 JMS 消息传递入门程序                                                                                                                                                                                                                                                                        |
| `spring-boot-starter-batch`                       | 使用 Spring Batch 的启动器                                                                                                                                                                                                                                                                                    |
| `spring-boot-starter-cache`                       | 使用 Spring 框架的缓存支持的入门程序                                                                                                                                                                                                                                                                                  |
| `spring-boot-starter-data-cassandra`              | 使用 Cassandra 分布式数据库和 Spring Data Cassandra 的入门程序                                                                                                                                                                                                                                                        |
| `spring-boot-starter-data-cassandra-reactive`     | 使用 Cassandra 分布式数据库和 Spring Data Cassandra Reactive 的入门程序                                                                                                                                                                                                                                               |
| `spring-boot-starter-data-couchbase`              | 使用 Couchbase 面向文档的数据库和 Spring Data Couchbase 的入门程序                                                                                                                                                                                                                                                      |
| `spring-boot-starter-data-couchbase-reactive`     | 使用 Couchbase 面向文档的数据库和 Spring Data Couchbase Reactive 的入门程序                                                                                                                                                                                                                                             |
| `spring-boot-starter-data-elasticsearch`          | 使用 Elasticsearch 搜索和分析引擎以及 Spring Data Elasticsearch 的入门程序                                                                                                                                                                                                                                              |
| `spring-boot-starter-data-jdbc`                   | 使用 Spring Data JDBC 的入门程序                                                                                                                                                                                                                                                                               |
| `spring-boot-starter-data-jpa`                    | 将 Spring Data JPA 与 Hibernate 结合使用的入门程序                                                                                                                                                                                                                                                                 |
| `spring-boot-starter-data-ldap`                   | 使用 Spring Data LDAP 的入门程序                                                                                                                                                                                                                                                                               |
| `spring-boot-starter-data-mongodb`                | 使用 MongoDB 面向文档的数据库和 Spring Data MongoDB 的入门程序                                                                                                                                                                                                                                                          |
| `spring-boot-starter-data-mongodb-reactive`       | 使用 MongoDB 面向文档的数据库和 Spring Data MongoDB Reactive 的入门程序                                                                                                                                                                                                                                                 |
| `spring-boot-starter-data-neo4j`                  | 使用 Neo4j 图形数据库和 Spring Data Neo4j 的入门程序                                                                                                                                                                                                                                                                 |
| `spring-boot-starter-data-r2dbc`                  | 使用 Spring Data R2DBC 的入门程序                                                                                                                                                                                                                                                                              |
| `spring-boot-starter-data-redis`                  | 通过 Spring Data Redis 和 Lettuce 客户端使用 Redis 键值数据存储的入门程序                                                                                                                                                                                                                                                  |
| `spring-boot-starter-data-redis-reactive`         | 使用 Redis 键值数据存储与 Spring Data Redis 反应式和 Lettuce 客户端的入门程序                                                                                                                                                                                                                                                |
| `spring-boot-starter-data-rest`                   | 使用 Spring Data REST 和 Spring MVC 通过 REST 公开 Spring Data 存储库的入门程序                                                                                                                                                                                                                                        |
| `spring-boot-starter-freemarker`                  | 使用 FreeMarker 视图构建 MVC Web 应用程序的入门程序                                                                                                                                                                                                                                                                    |
| `spring-boot-starter-graphql`                     | 使用 Spring GraphQL 构建 GraphQL 应用程序的入门程序                                                                                                                                                                                                                                                                  |
| `spring-boot-starter-groovy-templates`            | 使用 Groovy 模板视图构建 MVC Web 应用程序的入门程序                                                                                                                                                                                                                                                                      |
| `spring-boot-starter-hateoas`                     | 使用 Spring MVC 和 Spring HATEOAS 构建基于超媒体的 RESTful Web 应用程序的入门程序                                                                                                                                                                                                                                           |
| `spring-boot-starter-integration`                 | 使用 Spring Integration 的入门程序                                                                                                                                                                                                                                                                             |
| `spring-boot-starter-jdbc`                        | 将 JDBC 与 HikariCP 连接池结合使用的入门程序                                                                                                                                                                                                                                                                          |
| `spring-boot-starter-jersey`                      | 使用 JAX-RS 和 Jersey 构建 RESTful Web 应用程序的入门程序。替代方案[`spring-boot-starter-web`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-web)                                                                                                                              |
| `spring-boot-starter-jooq`                        | 使用 jOOQ 通过 JDBC 访问 SQL 数据库的入门程序。[`spring-boot-starter-data-jpa`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-data-jpa)或 的替代方案[`spring-boot-starter-jdbc`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-jdbc) |
| `spring-boot-starter-json`                        | 读取和写入 json 的 Starter                                                                                                                                                                                                                                                                                    |
| `spring-boot-starter-mail`                        | 使用 Java Mail 和 Spring Framework 的电子邮件发送支持的入门程序                                                                                                                                                                                                                                                          |
| `spring-boot-starter-mustache`                    | 使用 Mustache 视图构建 Web 应用程序的入门程序                                                                                                                                                                                                                                                                          |
| `spring-boot-starter-oauth2-authorization-server` | 使用 Spring 授权服务器功能的入门程序                                                                                                                                                                                                                                                                                  |
| `spring-boot-starter-oauth2-client`               | 使用 Spring Security 的 OAuth2/OpenID Connect 客户端功能的入门程序                                                                                                                                                                                                                                                   |
| `spring-boot-starter-oauth2-resource-server`      | 使用 Spring Security 的 OAuth2 资源服务器功能的入门程序                                                                                                                                                                                                                                                                |
| `spring-boot-starter-pulsar`                      | 使用 Spring for Apache Pulsar 的入门程序                                                                                                                                                                                                                                                                       |
| `spring-boot-starter-pulsar-reactive`             | 使用 Spring for Apache Pulsar Reactive 的入门程序                                                                                                                                                                                                                                                              |
| `spring-boot-starter-quartz`                      | 使用 Quartz 调度程序的入门程序                                                                                                                                                                                                                                                                                     |
| `spring-boot-starter-rsocket`                     | 用于构建 RSocket 客户端和服务器的 Starter                                                                                                                                                                                                                                                                           |
| `spring-boot-starter-security`                    | 使用 Spring Security 的入门程序                                                                                                                                                                                                                                                                                |
| `spring-boot-starter-test`                        | 用于使用 JUnit Jupiter、Hamcrest 和 Mockito 等库测试 Spring Boot 应用程序的 Starter                                                                                                                                                                                                                                    |
| `spring-boot-starter-thymeleaf`                   | 使用 Thymeleaf 视图构建 MVC Web 应用程序的入门程序                                                                                                                                                                                                                                                                     |
| `spring-boot-starter-validation`                  | 使用 Hibernate Validator 进行 Java Bean 验证的入门指南                                                                                                                                                                                                                                                             |
| `spring-boot-starter-web`                         | 用于使用 Spring MVC 构建 Web（包括 RESTful）应用程序的入门程序。使用Tomcat作为默认的嵌入式容器                                                                                                                                                                                                                                          |
| `spring-boot-starter-web-services`                | 使用 Spring Web 服务的入门程序                                                                                                                                                                                                                                                                                   |
| `spring-boot-starter-webflux`                     | 使用 Spring Framework 的 Reactive Web 支持构建 WebFlux 应用程序的入门程序                                                                                                                                                                                                                                               |
| `spring-boot-starter-websocket`                   | 使用 Spring Framework 的 MVC WebSocket 支持构建 WebSocket 应用程序的入门程序                                                                                                                                                                                                                                            |

除了应用程序启动器之外，还可以使用以下启动器来添加[*生产就绪*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator)功能：

| 姓名                             | 描述                                                      |
| ------------------------------ | ------------------------------------------------------- |
| `spring-boot-starter-actuator` | 使用 Spring Boot 的 Actuator 的入门程序，它提供生产就绪的功能来帮助您监视和管理应用程序 |

最后，Spring Boot 还包括以下启动器，如果您想排除或交换特定的技术方面，可以使用它们：

| 姓名                                  | 描述                                                                                                                                                                            |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spring-boot-starter-jetty`         | 使用 Jetty 作为嵌入式 servlet 容器的入门程序。替代方案[`spring-boot-starter-tomcat`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-tomcat)           |
| `spring-boot-starter-log4j2`        | 使用 Log4j2 进行日志记录的入门程序。替代方案[`spring-boot-starter-logging`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-logging)                  |
| `spring-boot-starter-logging`       | 使用 Logback 进行日志记录的启动器。默认日志记录启动器                                                                                                                                               |
| `spring-boot-starter-reactor-netty` | 使用 Reactor Netty 作为嵌入式反应式 HTTP 服务器的入门程序。                                                                                                                                      |
| `spring-boot-starter-tomcat`        | 使用 Tomcat 作为嵌入式 servlet 容器的入门程序。使用的默认 servlet 容器启动器[`spring-boot-starter-web`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-web) |
| `spring-boot-starter-undertow`      | 使用 Undertow 作为嵌入式 servlet 容器的入门程序。替代方案[`spring-boot-starter-tomcat`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#spring-boot-starter-tomcat)        |

[要了解如何交换技术方面，请参阅交换 Web 服务器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.use-another)和[日志系统](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.logging.log4j)的操作方法文档。

> 有关其他社区贡献的入门者的列表，请参阅GitHub 上模块`spring-boot-starters` 中的[自述文件。](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-starters/README.adoc)


# 6.2. 构建你的代码

Spring Boot 不需要任何特定的代码布局即可工作。不过，有一些最佳实践可以提供帮助。

> 如果您希望强制执行基于域的结构，请查看[Spring Modulith](https://spring.io/projects/spring-modulith#overview)。

**6.2.1. 使用“默认”包**

当一个类不包含`package`声明时，它被认为位于“默认包”中。通常不鼓励并且应该避免使用“默认包”。对于使用`@ComponentScan`、`@ConfigurationPropertiesScan`、`@EntityScan`或`@SpringBootApplication`注释的 Spring Boot 应用程序，它可能会导致特定问题，因为每个 jar 中的每个类都会被读取。

> 我们建议您遵循 Java 推荐的包命名约定并使用反向域名（例如`com.example.project`）。

**6.2.2. 找到主应用程序类**

我们通常建议您将主应用程序类放在根包中其他类之上。该[`@SpringBootApplication`注释](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.using-the-springbootapplication-annotation)通常放置在您的主类上，它隐式地定义了某些项目的基本“搜索包”。例如，如果您正在编写 JPA 应用程序，则使用带`@SpringBootApplication`注释的类的包来搜索`@Entity`项目。使用根包还允许组件扫描仅应用于您的项目。

如果您不想使用`@SpringBootApplication`，它导入的 `@EnableAutoConfiguration`和`@ComponentScan`注释定义了该行为，因此您也可以使用它们。

以下清单显示了典型的布局：

```
com
 +- example
     +- myapplication
         +- MyApplication.java
         |
         +- customer
         |   +- Customer.java
         |   +- CustomerController.java
         |   +- CustomerService.java
         |   +- CustomerRepository.java
         |
         +- order
             +- Order.java
             +- OrderController.java
             +- OrderService.java
             +- OrderRepository.java
```

`MyApplication.java`文件将声明该`main`方法以及基本的 `@SpringBootApplication`，如下所示：

```
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
​
}
```


# 6.3. 配置类

Spring Boot 支持基于 Java 的配置。尽管可以通过XML 源使用`SpringApplication`，但我们通常建议您的主要源是单个`@Configuration`类。通常，定义`main`方法的类是作为主要`@Configuration`类的良好候选者。

> 互联网上已经发布了许多使用 XML 配置的 Spring 配置示例。如果可能，请始终尝试使用等效的基于 Java 的配置。搜索`Enable*`注释可能是一个很好的起点。

**6.3.1. 导入附加配置类**

您无需将所有`@Configuration`内容都放入一个类中。`@Import`注释可用于导入其他配置类。或者，您可以使用`@ComponentScan`自动获取所有 Spring 组件，包括`@Configuration`类。

**6.3.2. 导入 XML 配置**

如果您绝对必须使用基于 XML 的配置，我们建议您仍然从`@Configuration`类开始。然后，您可以使用`@ImportResource`注释来加载 XML 配置文件。


# 6.4. 自动配置

Spring Boot 自动配置尝试根据您添加的 jar 依赖项自动配置您的 Spring 应用程序。例如，如果`HSQLDB`位于您的类路径中，并且您尚未手动配置任何数据库连接 Bean，则 Spring Boot 会自动配置内存数据库。

您需要通过将 `@EnableAutoConfiguration`或`@SpringBootApplication`注释添加到`@Configuration` 类之一来选择自动配置。

> 您应该只添加一个`@SpringBootApplication`或`@EnableAutoConfiguration`注释。我们通常建议您仅将其中之一添加到您的主要`@Configuration`类中。

**6.4.1. 逐步取代自动配置**

自动配置是非侵入性的。您可以随时开始定义自己的配置来替换自动配置的特定部分。例如，如果您添加自己的`DataSource` bean，则默认的嵌入式数据库支持将消失。

如果您需要了解当前正在应用什么自动配置以及原因，请使用`--debug`开关启动您的应用程序。这样做可以启用所选核心记录器的调试日志，并将条件报告记录到控制台。

**6.4.2. 禁用特定的自动配置类**

如果您发现正在应用您不想要的特定自动配置类，您可以使用 `@SpringBootApplication` 的排除属性来禁用它们，如以下示例所示：

```
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class MyApplication {
​
}
```

如果该类不在类路径上，则可以使用注释的`excludeName`属性并指定完全限定名称。如果您更喜欢使用`@EnableAutoConfiguration`而不是`@SpringBootApplication`，`exclude`和`excludeName`也可用。最后，您还可以使用`spring.autoconfigure.exclude` 属性控制要排除的自动配置类的列表。

> 您可以在注释级别和使用属性来定义排除。
>
> 尽管自动配置类是`public`，但该类被视为公共 API 的唯一方面是可用于禁用自动配置的类的名称。这些类的实际内容（例如嵌套配置类或 bean 方法）仅供内部使用，我们不建议直接使用它们。

**6.4.3. 自动配置包**

自动配置包是在扫描实体和 Spring Data 存储库等内容时，各种自动配置功能默认查找的包。`@EnableAutoConfiguration` 注解（直接或通过`@SpringBootApplication` 上的存在）确定默认的自动配置包。可以使用 `@AutoConfigurationPackage` 注释来配置其他包。


# 6.5. Spring Bean 和依赖注入

您可以自由地使用任何标准 Spring 框架技术来定义您的 bean 及其注入的依赖项。我们通常建议使用构造函数注入来连接依赖项并通过`@ComponentScan`查找 bean。

如果您按照上面的建议构建代码（将应用程序类定位在顶级包中），则可以在`@ComponentScan`不添加任何参数的情况下添加或使用`@SpringBootApplication`隐式包含`@ComponentScan`注释。您的所有应用程序组件（`@Component`、`@Service`、`@Repository`、`@Controller`和其他）都会自动注册为 Spring Bean。

以下示例显示了使用构造函数注入来获取所需 `@Service`bean 的`RiskAssessor` Bean：

```
@Service
public class MyAccountService implements AccountService {
​
    private final RiskAssessor riskAssessor;
​
    public MyAccountService(RiskAssessor riskAssessor) {
        this.riskAssessor = riskAssessor;
    }
​
    // ...
​
}
```

如果一个 bean 有多个构造函数，您需要通过`@Autowired`标记您希望 Spring 使用的构造函数：

```
@Service
public class MyAccountService implements AccountService {
​
    private final RiskAssessor riskAssessor;
​
    private final PrintStream out;
​
    @Autowired
    public MyAccountService(RiskAssessor riskAssessor) {
        this.riskAssessor = riskAssessor;
        this.out = System.out;
    }
​
    public MyAccountService(RiskAssessor riskAssessor, PrintStream out) {
        this.riskAssessor = riskAssessor;
        this.out = out;
    }
​
    // ...
​
}
```

> 请注意如何使用构造函数注入将`riskAssessor`字段标记为`final`，表示随后无法对其进行更改。


# 6.6. 使用@SpringBootApplication注解

许多 Spring Boot 开发人员喜欢他们的应用程序使用自动配置、组件扫描并能够在其“应用程序类”上定义额外的配置。可以使用单个`@SpringBootApplication`注释来启用这三个功能，即：

* `@EnableAutoConfiguration`：启用[Spring Boot的自动配置机制](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration)
* `@ComponentScan`：对应用程序所在的包启用`@Component`扫描（请参阅[最佳实践](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.structuring-your-code)）
* `@SpringBootConfiguration`：允许在上下文中注册额外的bean或导入额外的配置类。Spring 标准的`@Configuration`替代方案，有助于集成测试中的[配置检测。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.spring-boot-applications.detecting-configuration)

```
// Same as @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
​
}
```

> `@SpringBootApplication`还提供别名来自定义`@EnableAutoConfiguration`和`@ComponentScan`的属性。
>
> 这些功能都不是强制性的，您可以选择用它启用的任何功能来替换此单个注释。例如，您可能不想在应用程序中使用组件扫描或配置属性扫描：
>
> ```
> @SpringBootConfiguration(proxyBeanMethods = false)
> @EnableAutoConfiguration
> @Import({ SomeConfiguration.class, AnotherConfiguration.class })
> public class MyApplication {
> ​
>     public static void main(String[] args) {
>         SpringApplication.run(MyApplication.class, args);
>     }
> ​
> }
> ```
>
> 在此示例中，`MyApplication`与任何其他 Spring Boot 应用程序一样，只是不会自动检测`@Component`-annotated 类和`@ConfigurationProperties`-annotated 类，并且显式导入用户定义的 bean（请参阅 `@Import`参考资料）。


# 6.7. 运行您的应用程序

将应用程序打包为 jar 并使用嵌入式 HTTP 服务器的最大优点之一是您可以像运行任何其他应用程序一样运行您的应用程序。该示例适用于调试Spring Boot应用程序。您不需要任何特殊的 IDE 插件或扩展。

> 本节仅涵盖基于 jar 的包装。如果您选择将应用程序打包为 war 文件，请参阅您的服务器和 IDE 文档。

**6.7.1. 从 IDE 运行**

您可以从 IDE 将 Spring Boot 应用程序作为 Java 应用程序运行。但是，您首先需要导入您的项目。导入步骤因您的 IDE 和构建系统而异。大多数IDE都可以直接导入Maven项目。例如，Eclipse 用户可以从`File`菜单中选择`Import…`→ `Existing Maven Projects`。

如果您无法将项目直接导入 IDE，则可以使用构建插件生成 IDE 元数据。[Maven 包括Eclipse](https://maven.apache.org/plugins/maven-eclipse-plugin/)和[IDEA](https://maven.apache.org/plugins/maven-idea-plugin/)的插件。[Gradle 为各种 IDE](https://docs.gradle.org/current/userguide/userguide.html)提供插件。

> 如果您不小心运行 Web 应用程序两次，您会看到“端口已在使用中”错误。Spring Tools 用户可以使用`Relaunch`按钮而不是`Run`按钮来确保关闭任何现有实例。

**6.7.2. 作为打包应用程序运行**

如果您使用 Spring Boot Maven 或 Gradle 插件创建可执行 jar，则可以使用`java -jar` 运行应用程序，如以下示例所示：

```
$ java -jar target/myapplication-0.0.1-SNAPSHOT.jar
```

还可以运行启用了远程调试支持的打包应用程序。这样做可以让您将调试器附加到打包的应用程序中，如以下示例所示：

```
$ java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n \
       -jar target/myapplication-0.0.1-SNAPSHOT.jar
```

**6.7.3. 使用 Maven 插件**

Spring Boot Maven 插件包含一个可用于快速编译和运行应用程序的`run`目标。应用程序以分解形式运行，就像在 IDE 中一样。以下示例显示了运行 Spring Boot 应用程序的典型 Maven 命令：

```
$ mvn spring-boot:run
```

您可能还想使用`MAVEN_OPTS`操作系统环境变量，如以下示例所示：

```
$ export MAVEN_OPTS=-Xmx1024m
```

**6.7.4. 使用 Gradle 插件**

Spring Boot Gradle 插件还包含一个可用于以分解形式运行应用程序的`bootRun`任务。每当您应用`org.springframework.boot`和`java`插件时都会添加`bootRun`任务，如以下示例所示：

```
$ gradle bootRun
```

您可能还想使用`JAVA_OPTS`操作系统环境变量，如以下示例所示：

```
$导出JAVA_OPTS=-Xmx1024m
```

**6.7.5. 热插拔**

由于 Spring Boot 应用程序是普通的 Java 应用程序，因此 JVM 热插拔应该可以开箱即用。JVM 热交换在某种程度上受到它可以替换的字节码的限制。对于更完整的解决方案，可以使用[JRebel 。](https://www.jrebel.com/products/jrebel)

`spring-boot-devtools`模块还包括对快速应用程序重新启动的支持。有关详细信息，请参阅[热插拔“操作方法”](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.hotswapping)。


# 6.8. 开发者工具

Spring Boot 包含一组额外的工具，可以使应用程序开发体验更加愉快。`spring-boot-devtools`模块可以包含在任何项目中以提供额外的开发时功能。要包含 devtools 支持，请将模块依赖项添加到您的构建中，如以下 Maven 和 Gradle 列表所示：

*Maven*

```
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
```

*Gradle*

```
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-devtools")
}
```

> 开发工具可能会导致类加载问题，特别是在多模块项目中。 [诊断类加载问题](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.diagnosing-classloading-issues)解释了如何诊断和解决这些问题。
>
> 运行完全打包的应用程序时，开发人员工具会自动禁用。如果您的应用程序是从`java -jar`特殊的类加载器启动的，则它被视为“生产应用程序”。您可以使用`spring.devtools.restart.enabled`系统属性来控制此行为。要启用 devtools，无论用于启动应用程序的类加载器如何，请设置`-Dspring.devtools.restart.enabled=true`系统属性。不得在运行 devtools 存在安全风险的生产环境中执行此操作。要禁用开发工具，请排除依赖项或设置`-Dspring.devtools.restart.enabled=false`系统属性。
>
> 在 Maven 中将依赖项标记为可选或使用`developmentOnly`Gradle 中的配置（如上所示）可防止 devtools 传递应用于使用您的项目的其他模块。
>
> 重新打包的存档默认不包含开发工具。如果你想使用[某个远程开发工具功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.remote-applications)，你需要包含它。使用 Maven 插件时，将该`excludeDevtools`属性设置为`false`。使用 Gradle 插件时，[配置任务的类路径以包含`developmentOnly`配置](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/htmlsingle/#packaging-executable-configuring-including-development-only-dependencies).

**6.8.1. 诊断类加载问题**

[如重新启动与重新加载](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.restart-vs-reload)部分所述，重新启动功能是通过使用两个类加载器来实现的。对于大多数应用程序来说，这种方法效果很好。但是，它有时会导致类加载问题，特别是在多模块项目中。

要诊断类加载问题是否确实是由 devtools 及其两个类加载器引起的，[请尝试禁用 restart](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.disable)。如果这解决了您的问题，[请自定义重新启动类加载器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.customizing-the-classload)以包含您的整个项目。

**6.8.2. 属性默认值**

Spring Boot 支持的一些库使用缓存来提高性能。例如，[模板引擎](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.template-engines)会缓存编译后的模板，以避免重复解析模板文件。此外，Spring MVC 可以在提供静态资源时向响应添加 HTTP 缓存标头。

虽然缓存在生产中非常有益，但在开发过程中可能会适得其反，使您无法看到刚刚在应用程序中所做的更改。因此，spring-boot-devtools 默认禁用缓存选项。

缓存选项通常通过`application.properties`文件中的设置进行配置。例如，Thymeleaf 就提供`spring.thymeleaf.cache`属性。该模块不需要手动设置这些属性，而是`spring-boot-devtools`自动应用合理的开发时配置。

下表列出了所有应用的属性：

| 姓名                                               | 默认值               |
| ------------------------------------------------ | ----------------- |
| `server.error.include-binding-errors`            | `always`          |
| `server.error.include-message`                   | `always`          |
| `server.error.include-stacktrace`                | `always`          |
| `server.servlet.jsp.init-parameters.development` | `true`            |
| `server.servlet.session.persistent`              | `true`            |
| `spring.docker.compose.readiness.wait`           | `only-if-started` |
| `spring.freemarker.cache`                        | `false`           |
| `spring.graphql.graphiql.enabled`                | `true`            |
| `spring.groovy.template.cache`                   | `false`           |
| `spring.h2.console.enabled`                      | `true`            |
| `spring.mustache.servlet.cache`                  | `false`           |
| `spring.mvc.log-resolved-exception`              | `true`            |
| `spring.reactor.netty.shutdown-quiet-period`     | `0s`              |
| `spring.template.provider.cache`                 | `false`           |
| `spring.thymeleaf.cache`                         | `false`           |
| `spring.web.resources.cache.period`              | `0`               |
| `spring.web.resources.chain.cache`               | `false`           |

如果您不希望应用属性默认值，您可以在`application.properties`将相应的`spring.devtools.add-properties`设置为 `false`

由于您在开发 Spring MVC 和 Spring WebFlux 应用程序时需要有关 Web 请求的更多信息，因此开发人员工具建议您为`web`日志记录组启用`DEBUG`日志记录。这将为您提供有关传入请求、正在处理该请求的处理程序、响应结果以及其他详细信息的信息。如果您希望记录所有请求详细信息（包括潜在的敏感信息），您可以打开`spring.mvc.log-request-details`或`spring.codec.log-request-details`配置属性。

**6.8.3. 自动重启**

`spring-boot-devtools`每当类路径上的文件发生更改时，使用自动重新启动的应用程序。在 IDE 中工作时，这可能是一个有用的功能，因为它为代码更改提供了非常快速的反馈循环。默认情况下，类路径上指向目录的任何条目都会受到监视以了解更改。请注意，某些资源（例如静态资产和视图模板）[不需要重新启动应用程序](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.excluding-resources)。

> **触发重启**
>
> 由于 DevTools 监视类路径资源，触发重新启动的唯一方法是更新类路径。无论您使用 IDE 还是构建插件之一，都必须重新编译修改的文件才能触发重新启动。更新类路径的方式取决于您使用的工具：
>
> * 在 Eclipse 中，保存修改的文件会导致类路径更新并触发重新启动。
> * 在 IntelliJ IDEA 中，构建项目 ( `Build +→+ Build Project`) 具有相同的效果。
> * 如果使用构建插件，运行Maven`mvn compile` 或Gradle 将 `gradle build`触发重新启动。
>
> 如果您使用构建插件重新启动 Maven 或 Gradle，则必须将设置保留`forking`为`enabled`. 如果禁用分叉，则将不会创建 devtools 使用的隔离应用程序类加载器，并且重新启动将无法正常运行。
>
> 与 LiveReload 一起使用时，自动重启效果非常好。 [有关详细信息，请参阅 LiveReload 部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.livereload)。如果您使用 JRebel，则会禁用自动重新启动，以支持动态类重新加载。仍然可以使用其他开发工具功能（例如 LiveReload 和属性覆盖）。
>
> DevTools 依赖应用程序上下文的关闭挂钩在重新启动期间将其关闭。如果您禁用了关闭挂钩 (`SpringApplication.setRegisterShutdownHook(false)` )，它将无法正常工作。
>
> DevTools 需要通过`ApplicationContext`自定义`ResourceLoader`. 如果您的应用程序已经提供了一个`ApplicationContext`，那么它将被包装。将不支持`ApplicationContext`直接重写方法`getResource`。
>
> 使用 AspectJ 编织时不支持自动重启。

> **重新启动与重新加载**
>
> Spring Boot 提供的重启技术通过使用两个类加载器来工作。不更改的类（例如，来自第三方 jar 的类）将加载到*基*类加载器中。您正在积极开发的类将加载到*重新启动的*类加载器中。当应用程序重新启动时，*重新启动的*类加载器将被丢弃并创建一个新的类加载器。这种方法意味着应用程序重新启动通常比“冷启动”快得多，因为*基类*加载器已经可用并已填充。
>
> 如果您发现应用程序的重新启动速度不够快或者遇到类加载问题，您可以考虑重新加载技术，例如ZeroTurnaround 的[JRebel 。](https://jrebel.com/software/jrebel/)这些工作原理是在加载类时重写类，使它们更适合重新加载。

**记录条件计算的变化**

默认情况下，每次应用程序重新启动时，都会记录一份显示增量的条件计算报告。当您进行更改（例如添加或删除 Bean 以及设置配置属性）时，该报告会显示应用程序自动配置的更改。

要禁用报告日志记录，请设置以下属性：

```
spring.devtools.restart.log-condition-evaluation-delta=false
```

**排除资源**

某些资源在更改时不一定需要触发重新启动。例如，Thymeleaf 模板可以就地编辑。默认情况下，更改`/META-INF/maven`、`/META-INF/resources`、`/resources`、`/static`、`/public`或`/templates`中的资源不会触发重新启动，但会触发[实时重新加载](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.livereload)。如果您想自定义这些排除项，可以使用该`spring.devtools.restart.exclude`属性。例如，要仅排除`/static`，`/public`您可以设置以下属性：

```
spring.devtools.restart.exclude=static/**,public/**
```

> 如果您想保留这些默认值并*添加*其他排除项，请改用`spring.devtools.restart.additional-exclude`属性

**监控其他路径**

当您对不在类路径上的文件进行更改时，您可能希望重新启动或重新加载应用程序。为此，请使用该`spring.devtools.restart.additional-paths`属性配置其他路径以监视更改。您可以使用[前面描述的](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.excluding-resources)`spring.devtools.restart.exclude`属性来控制其他路径下的更改是否触发完全重新启动或[实时重新加载](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.livereload)。

**禁用重新启动**

如果您不想使用重新启动功能，可以使用`spring.devtools.restart.enabled` 属性将其禁用。在大多数情况下，您可以在您的`application.properties`文件中设置此属性（这样做仍然会初始化重新启动类加载器，但它不会监视文件更改）。

如果您需要*完全*禁用重新启动支持（例如，因为它不适用于特定库），则需要在 `SpringApplication.run(…)`之前将`spring.devtools.restart.enabled` `System`属性设置为`false`，如以下示例所示：

```
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(MyApplication.class, args);
    }
​
}
```

**使用触发文件**

如果您使用连续编译更改的文件的 IDE，您可能更愿意仅在特定时间触发重新启动。为此，您可以使用“触发文件”，这是一个特殊文件，当您想要实际触发重新启动检查时必须修改该文件。

> 对文件的任何更新都会触发检查，但只有在 Devtools 检测到有事情要做时才会真正重新启动。

要使用触发器文件，请将该`spring.devtools.restart.trigger-file`属性设置为触发器文件的名称（不包括任何路径）。触发器文件必须出现在类路径上的某个位置。

例如，如果您有一个具有以下结构的项目：

```
src
+- main
   +- resources
      +- .reloadtrigger
```

那么你的`trigger-file`属性将是：

```
spring.devtools.restart.trigger-file=.reloadtrigger
```

现在只有在更新时才会重新启动`src/main/resources/.reloadtrigger`。

> 您可能希望设置`spring.devtools.restart.trigger-file`为[全局设置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.globalsettings)，以便所有项目都以相同的方式运行。

某些 IDE 具有使您无需手动更新触发器文件的功能。 [Spring Tools for Eclipse](https://spring.io/tools)和[IntelliJ IDEA（终极版）](https://www.jetbrains.com/idea/)都有这样的支持。使用 Spring Tools，您可以使用控制台视图中的“重新加载”按钮（只要您的`trigger-file`名称为`.reloadtrigger`）。对于 IntelliJ IDEA，您可以按照[其文档中的说明](https://www.jetbrains.com/help/idea/spring-boot.html#application-update-policies)进行操作。

**自定义重启类加载器**

[如前面的“重新启动与重新加载”](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.restart-vs-reload)部分所述，重新启动功能是通过使用两个类加载器来实现的。如果这导致问题，您可能需要自定义由哪个类加载器加载的内容。

默认情况下，IDE 中任何打开的项目都会使用“重新启动”类加载器加载，任何常规`.jar`文件都会使用“基本”类加载器加载。如果您使用`mvn spring-boot:run`或者`gradle bootRun`，情况也是如此：包含`@SpringBootApplication`的项目使用“重新启动”类加载器加载，其他所有内容都使用“基本”类加载器加载。

您可以通过创建`META-INF/spring-devtools.properties`文件来指示 Spring Boot 使用不同的类加载器加载项目的部分内容。`spring-devtools.properties`文件可以包含前缀为`restart.exclude`和`restart.include` 的属性。这些`include`元素是应该被拉入“重新启动”类加载器的项目，这些`exclude`元素是应该被推入“基本”类加载器的项目。该属性的值是应用于类路径的正则表达式模式，如以下示例所示：

```
restart.exclude.companycommonlibs=/mycorp-common-[\\w\\d-\\.]+\\.jar
restart.include.projectcommon=/mycorp-myproj-[\\w\\d-\\.]+\\.jar
```

> 所有属性键必须是唯一的。只要一个属性以`restart.include.`或`restart.exclude.`开始，就会被采纳。
>
> 所有`META-INF/spring-devtools.properties`来自类路径的内容都会被加载。您可以将文件打包到项目内或项目使用的库中。

**已知限制**

重新启动功能不适用于使用标准`ObjectInputStream`. 如果您需要反序列化数据，可能需要结合使用Spring的`ConfigurableObjectInputStream`与`Thread.currentThread().getContextClassLoader()`.

不幸的是，一些第三方库在不考虑上下文类加载器的情况下进行反序列化。如果您发现此类问题，您需要向原作者请求修复。

**6.8.4. 实时重载**

`spring-boot-devtools`模块包括一个嵌入式 LiveReload 服务器，可用于在资源更改时触发浏览器刷新。LiveReload 浏览器扩展可免费用于 Chrome、Firefox 和 Safari。您可以通过在所选浏览器的市场或商店中搜索“LiveReload”来找到这些扩展。

如果您不想在应用程序运行时启动 LiveReload 服务器，可以将`spring.devtools.livereload.enabled`属性设置为`false`。

> 您一次只能运行一台 LiveReload 服务器。在启动应用程序之前，请确保没有其他 LiveReload 服务器正在运行。如果您从 IDE 启动多个应用程序，则只有第一个应用程序具有 LiveReload 支持。
>
> 要在文件更改时触发 LiveReload，必须启用 [自动重新启动。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart)

**6.8.5. 全局设置**

您可以通过将以下任意文件添加到`$HOME/.config/spring-boot`目录来配置全局 devtools 设置：

1. `spring-boot-devtools.properties`
2. `spring-boot-devtools.yaml`
3. `spring-boot-devtools.yml`

添加到这些文件的任何属性都适用于您计算机上使用 devtools 的*所有*Spring Boot 应用程序。例如，要将重新启动配置为始终使用[触发器文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart.triggerfile)，您可以将以下属性添加到`spring-boot-devtools`文件中：

```
spring.devtools.restart.trigger-file=.reloadtrigger
```

默认情况下，`$HOME`是用户的主目录。要自定义此位置，请设置`SPRING_DEVTOOLS_HOME`环境变量或`spring.devtools.home`系统属性。

> 如果在`$HOME/.config/spring-boot` 中找不到 devtools 配置文件，则会在`$HOME`目录的根目录中搜索是否存在`.spring-boot-devtools.properties`文件。这允许您与不支持该`$HOME/.config/spring-boot`位置的旧版本 Spring Boot 上的应用程序共享 devtools 全局配置。
>
> devtools properties/yaml 文件不支持配置文件。
>
> 在 `.spring-boot-devtools.properties`中激活的任何配置文件都不会影响特定于配置文件的配置文件的加载。 YAML 和 Properties 文件中的配置文件特定文件名（格式为 `spring-boot-devtools-.properties`）和 `spring.config.activate.on-profile`文档不受支持。

**配置文件系统观察器**

[FileSystemWatcher 的](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/filewatch/FileSystemWatcher.java)工作原理是按照一定的时间间隔轮询类更改，然后等待预定义的安静期以确保不再有更改。由于 Spring Boot 完全依赖 IDE 来编译文件并将文件复制到 Spring Boot 可以读取它们的位置，因此您可能会发现，有时当 devtools 重新启动应用程序时，某些更改不会反映出来。如果您经常观察到此类问题，请尝试将`spring.devtools.restart.poll-interval`和`spring.devtools.restart.quiet-period`参数增加到适合您的开发环境的值：

```
spring.devtools.restart.poll-interval=2s
spring.devtools.restart.quiet-period=1s
```

现在每 2 秒轮询一次受监视的类路径目录是否有更改，并保持 1 秒的安静期以确保没有其他类更改。

**6.8.6. 远程应用程序**

Spring Boot开发者工具不仅限于本地开发。您还可以在远程运行应用程序时使用多种功能。远程支持是可选的，因为启用它可能会带来安全风险。仅当在受信任的网络上运行或使用 SSL 保护时才应启用它。如果您无法使用这些选项，则不应使用 DevTools 的远程支持。您永远不应该启用对生产部署的支持。

要启用它，您需要确保它`devtools`包含在重新打包的存档中，如下列表所示：

```
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludeDevtools>false</excludeDevtools>
            </configuration>
        </plugin>
    </plugins>
</build>
```

然后你需要设置`spring.devtools.remote.secret`属性。与任何重要的密码或秘密一样，该值应该是唯一且强大的，以便无法猜测或暴力破解。

远程开发工具支持分为两部分：接受连接的服务器端端点和在 IDE 中运行的客户端应用程序。设置`spring.devtools.remote.secret`属性后，服务器组件会自动启用。客户端组件必须手动启动。

> Spring WebFlux 应用程序不支持远程开发工具。

**运行远程客户端应用程序**

远程客户端应用程序设计为在 IDE 中运行。您需要使用`org.springframework.boot.devtools.RemoteSpringApplication`与连接到的远程项目相同的类路径来运行。应用程序的唯一必需参数是它连接的远程 URL。

例如，如果您使用 Eclipse 或 Spring Tools，并且您有一个名为`my-app`您已部署到 Cloud Foundry 的项目，则您将执行以下操作：

* 从`Run`菜单中选择`Run Configurations…`。
* 创建一个新的`Java Application`“启动配置”。
* 浏览`my-app`项目。
* `org.springframework.boot.devtools.RemoteSpringApplication`作为主类使用。
* 添加`https://myapp.cfapps.io`到`Program arguments`（或任何您的远程 URL）。

正在运行的远程客户端可能类似于以下列表：

```
  。____ _ __ _ _
 /\\ / ___'_ __ _ _(_)_ __ __ _ ___ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | | _ \___ _ __ ___| |_ ___ \ \ \ \
 \\/ ___)| |_)| | | | | || (_| []::::::[] / -_) ' \/ _ \ _/ -_) ) ) ) )
  ' |____| .__|_| |_|_| |_\__, | |_|_\___|_|_|_\___/\__\___|/ / / /
 ========|_|==============|___/====================== =============/_/_/_/
 :: Spring Boot 远程 :: (v3.2.0)
​
2023-11-23T13:40:36.387Z INFO 39306 --- [ main] osbdevtools.RemoteSpringApplication ：使用 PID 39306 的 Java 17.0.9 启动 RemoteSpringApplication v3.2.0 (/Users/myuser/.m2/repository/org/springframework/ boot/spring-boot-devtools/3.2.0/spring-boot-devtools-3.2.0.jar 由 myuser 在 /opt/apps/ 中启动）
2023-11-23T13:40:36.394Z INFO 39306 --- [ main] osbdevtools.RemoteSpringApplication ：没有设置活动配置文件，回退到 1 个默认配置文件：“default”
2023-11-23T13:40:36.794Z INFO 39306 --- [ main] osbdaOptionalLiveReloadServer ：LiveReload 服务器正在端口 35729 上运行
2023-11-23T13:40:36.823Z INFO 39306 --- [ main] osbdevtools.RemoteSpringApplication ：在 0.923 秒内启动 RemoteSpringApplication （进程运行 1.313）
```

> 由于远程客户端使用与真实应用程序相同的类路径，因此它可以直接读取应用程序属性。这就是`spring.devtools.remote.secret`读取属性并将其传递到服务器进行身份验证的方式。
>
> 始终建议使用`https://`作为连接协议，以便对流量进行加密并且密码无法被拦截。
>
> 如果需要使用代理访问远程应用程序，请配置`spring.devtools.remote.proxy.host`和`spring.devtools.remote.proxy.port`属性。

**远程更新**

[远程客户端以与本地重新启动](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.restart)相同的方式监视应用程序类路径的更改。任何更新的资源都会被推送到远程应用程序并（*如果需要*）触发重新启动。如果您迭代使用本地没有的云服务的功能，这会很有帮助。一般来说，远程更新和重新启动比完整的重建和部署周期要快得多。

在较慢的开发环境中，可能会出现安静期不够的情况，类中的更改可能会被分成批次。第一批类更改上传后，服务器将重新启动。由于服务器正在重新启动，因此无法将下一批发送到应用程序。

这通常通过`RemoteSpringApplication`日志中有关无法上传某些类的警告以及随后的重试来体现。但也可能导致应用代码不一致，第一批变更上传后无法重启。如果您经常观察到此类问题，请尝试将`spring.devtools.restart.poll-interval`和`spring.devtools.restart.quiet-period`参数增加到适合您的开发环境的值。有关配置这些属性的信息，请参阅[配置文件系统观察器部分。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.globalsettings.configuring-file-system-watcher)

> 仅当远程客户端运行时才会监视文件。如果在启动远程客户端之前更改文件，则该文件不会推送到远程服务器。


# 6.9. 打包您的生产应用程序

可执行 jar 可用于生产部署。由于它们是独立的，因此也非常适合基于云的部署。

对于其他“生产就绪”功能，例如运行状况、审计和指标 REST 或 JMX 端点，请考虑添加`spring-boot-actuator`. 有关详细信息，请参阅[*生产就绪功能。*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator)


# 6.10. 接下来读什么

您现在应该了解如何使用 Spring Boot 以及应该遵循的一些最佳实践。现在，您可以继续深入了解特定的[*Spring Boot 功能*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features)，或者您可以跳过并阅读 Spring Boot 的“[生产就绪](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator)”方面。


# 7.核心特性

本节深入探讨 Spring Boot 的细节。在这里您可以了解您可能想要使用和自定义的关键功能。如果您还没有这样做，您可能需要阅读“[入门](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started)”和“[使用 Spring Boot 进行开发](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using)”部分，以便您有良好的基础知识。


# 7.1. SpringApplication

#### 7.1. Spring应用程序

`SpringApplication`类提供了一种方便的方式来引导从`main()`方法启动的 Spring 应用程序。在许多情况下，您可以委托给静态`SpringApplication.run`方法，如以下示例所示：

```
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
​
}
```

当您的应用程序启动时，您应该看到类似于以下输出的内容：

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.0)
​
2023-11-23T13:40:37.786Z  INFO 39373 --- [           main] o.s.b.d.f.logexample.MyApplication       : Starting MyApplication using Java 17.0.9 with PID 39373 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-11-23T13:40:37.791Z  INFO 39373 --- [           main] o.s.b.d.f.logexample.MyApplication       : No active profile set, falling back to 1 default profile: "default"
2023-11-23T13:40:39.237Z  INFO 39373 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2023-11-23T13:40:39.251Z  INFO 39373 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-11-23T13:40:39.252Z  INFO 39373 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.16]
2023-11-23T13:40:39.327Z  INFO 39373 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-11-23T13:40:39.329Z  INFO 39373 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1448 ms
2023-11-23T13:40:39.863Z  INFO 39373 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
2023-11-23T13:40:39.876Z  INFO 39373 --- [           main] o.s.b.d.f.logexample.MyApplication       : Started MyApplication in 2.652 seconds (process running for 3.034)
```

默认情况下，`INFO`会显示日志消息，包括一些相关的启动详细信息，例如启动应用程序的用户。如果您需要除 `INFO`之外的日志级别，您可以如[日志级别](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging.log-levels)中所述设置它。应用程序版本是使用主应用程序类包中的实现版本确定的。`spring.main.log-startup-info`可以通过设置为`false`来关闭启动信息记录。这也将关闭应用程序活动配置文件的日志记录。

> 要在启动期间添加额外的日志记录，您可以在`SpringApplication` 的子类中重写 `logStartupInfo(boolean)`。

**7.1.1. 启动失败**

如果您的应用程序无法启动，注册的`FailureAnalyzers`可以提供专门的错误消息和解决问题的具体操作。例如，如果您在`8080`端口上启动 Web 应用程序并且该端口已在使用中，您应该会看到类似于以下消息的内容：

```
***************************
APPLICATION FAILED TO START
***************************
​
Description:
​
Embedded servlet container failed to start. Port 8080 was already in use.
​
Action:
​
Identify and stop the process that is listening on port 8080 or configure this application to listen on another port.
```

> Spring Boot 提供了许多`FailureAnalyzer`实现，您可以[添加自己的](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.application.failure-analyzer).

如果故障分析器没有能够处理异常，您仍然可以显示完整的情况报告，以更好地了解出了什么问题。为此，您需要[启用](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging.log-levels)[该`debug`属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config)或对`org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener`[启用`DEBUG`日志记录](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging.log-levels).

例如，如果您使用`java -jar` 运行应用程序，则可以按如下方式启用`debug`属性：

```
$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug
```

**7.1.2. 延迟初始化**

`SpringApplication`允许应用程序延迟初始化。当启用延迟初始化时，bean 将在需要时创建，而不是在应用程序启动期间创建。因此，启用延迟初始化可以减少应用程序启动所需的时间。在 Web 应用程序中，启用延迟初始化将导致许多与 Web 相关的 Bean 在收到 HTTP 请求之前不会被初始化。

延迟初始化的一个缺点是它可能会延迟发现应用程序的问题。如果延迟初始化配置错误的 bean，则在启动期间将不再发生故障，并且只有在初始化 bean 时问题才会变得明显。还必须注意确保 JVM 有足够的内存来容纳应用程序的所有 Bean，而不仅仅是那些在启动期间初始化的 Bean。由于这些原因，默认情况下不启用延迟初始化，建议在启用延迟初始化之前微调 JVM 的堆大小。

可以使用`SpringApplicationBuilder`中的`lazyInitialization`方法或`SpringApplication`中的`setLazyInitialization` 方法以编程方式启用延迟初始化。或者，可以使用`spring.main.lazy-initialization`属性来启用它，如以下示例所示：

```
spring.main.lazy-initialization=true
```

> 如果要禁用某些 bean 的延迟初始化，同时对应用程序的其余部分使用延迟初始化，则可以使用`@Lazy(false)`注释显式将其延迟属性设置为 false 。

**7.1.3. 自定义横幅**

可以通过将`banner.txt`文件添加到类路径或将`spring.banner.location`属性设置为此类文件的位置来更改启动时打印的横幅。如果文件的编码不是 UTF-8，则需要设置`spring.banner.charset`.

在`banner.txt`文件中，您可以使用`Environment`中可用的任何键以及以下任何占位符：

| 变量                                                                                 | 描述                                                                         |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `${application.version}`                                                           | 您的应用程序的版本号，如 中声明的那样`MANIFEST.MF`。例如，`Implementation-Version: 1.0`打印为`1.0`. |
| `${application.formatted-version}`                                                 | 您的应用程序的版本号，如 `MANIFEST.MF`中声明的和显示格式的（用方括号括起来并以`v` 为前缀）。例如`(v1.0)`。         |
| `${spring-boot.version}`                                                           | 您正在使用的 Spring Boot 版本。例如`3.2.0`。                                           |
| `${spring-boot.formatted-version}`                                                 | 您正在使用的 Spring Boot 版本，已格式化以供显示（用方括号括起来并以 `v`为前缀）。例如`(v3.2.0)`。             |
| `${Ansi.NAME}`（或者`${AnsiColor.NAME}`，`${AnsiBackground.NAME}`，`${AnsiStyle.NAME}`） | 其中 NAME 是 ANSI 转义码的名称。有关详细信息，请参阅 `AnsiPropertySource`。                     |
| `${application.title}`                                                             | 您的应用的标题，如 `MANIFEST.MF`中声明的那样。例如`Implementation-Title: MyApp`打印为`MyApp`.   |

> `SpringApplication.setBanner(…)`如果您想以编程方式生成横幅，可以使用 该方法。使用该`org.springframework.boot.Banner`接口并实现您自己的`printBanner()`方法。

您还可以使用`spring.main.banner-mode`属性来确定是否必须在`System.out`( `console`)打印横幅、将其发送到配置的记录器 ( `log`)，还是根本不生成横幅 ( `off`)。

打印的横幅以以下名称注册为单例 bean：`springBootBanner`。

> 仅当您将`java -jar`或`java -cp` 与 Spring Boot 启动器一起使用时，`application.title`、`application.version` 和 `application.formatted-version` 属性才可用。如果您运行解压的 jar 并使用`java -cp` 启动它或将应用程序作为本机映像运行，则不会解析这些值。
>
> 如果使用`application.`属性文件，则需要使用`java -jar` 将应用程序作为打包的 jar 启动，或使用 `java org.springframework.boot.loader.launch.JarLauncher` 作为解压的 jar 启动。这将初始化`application`。这将会在构建类路径和启动应用程序之前，设置横幅属性。

**7.1.4. 定制 SpringApplication**

如果`SpringApplication`的默认设置不符合您的口味，您可以创建一个本地实例并对其进行自定义。例如，要关闭横幅，您可以编写：

```
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }
​
}
```

> 传递给`SpringApplication`构造函数的参数是 Spring bean 的配置源。在大多数情况下，这些是对`@Configuration`类的引用，但它们也可以是直接引用`@Component`类。

也可以使用`application.properties`文件进行`SpringApplication`配置。有关详细信息，请参阅[*外部化配置*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config)。

有关配置选项的完整列表，请参阅[`SpringApplication`Javadoc](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/SpringApplication.html)。

**7.1.5. Fluent 构建器 API**

如果您需要构建层次结构（具有父/子关系的多个上下文）`ApplicationContext`，或者如果您更喜欢使用“流畅”构建器 API，则可以使用`SpringApplicationBuilder`.

`SpringApplicationBuilder`允许您将多个方法调用链接在一起，并包含可让您创建层次结构的`parent`和`child`方法，如以下示例所示：

```
new SpringApplicationBuilder().sources(Parent.class)
    .child(Application.class)
    .bannerMode(Banner.Mode.OFF)
    .run(args);
```

创建层次结构`ApplicationContext`时存在一些限制。例如，Web 组件**必须**包含在子上下文中，并且`Environment`同样适用于父上下文和子上下文。有关完整详细信息， 请参阅[`SpringApplicationBuilder`Javadoc 。](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/builder/SpringApplicationBuilder.html)

**7.1.6. 应用程序可用性**

[当部署在平台上时，应用程序可以使用Kubernetes Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)等基础设施向平台提供有关其可用性的信息。Spring Boot 包括对常用的“活跃”和“就绪”可用性状态的开箱即用支持。如果您使用 Spring Boot 的“执行器”支持，那么这些状态将作为运行状况端点组公开。

此外，您还可以通过将`ApplicationAvailability`接口注入到您自己的bean中来获取可用性状态。

**活性状态**

应用程序的“活跃”状态表明其内部状态是否允许其正常工作，或者在当前发生故障时自行恢复。损坏的“Liveness”状态意味着应用程序处于无法恢复的状态，基础设施应该重新启动应用程序。

> 一般来说，“Liveness”状态不应该基于外部检查，例如[Health 检查](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.endpoints.health)。如果确实如此，发生故障的外部系统（数据库、Web API、外部缓存）将触发整个平台的大规模重启和级联故障。

Spring Boot 应用程序的内部状态主要由 Spring `ApplicationContext`表示。如果应用程序上下文已成功启动，Spring Boot 会假定应用程序处于有效状态。一旦上下文刷新，应用程序就被视为处于活动状态，请参阅[Spring Boot 应用程序生命周期和相关应用程序事件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.application-events-and-listeners)。

**准备状态**

应用程序的“就绪”状态表明应用程序是否已准备好处理流量。失败的“就绪”状态告诉平台它现在不应该将流量路由到应用程序。这通常发生在启动期间、处理`CommandLineRunner`和`ApplicationRunner`组件时，或者在应用程序认为太忙而无法承受额外流量的任何时间。

一旦调用应用程序和命令行运行程序，应用程序就被视为准备就绪，请参阅[Spring Boot 应用程序生命周期和相关应用程序事件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.application-events-and-listeners)。

> 预计在启动期间运行的任务应该由`CommandLineRunner`和`ApplicationRunner`组件执行，而不是使用 Spring 组件生命周期回调，例如`@PostConstruct`.

**管理应用程序可用性状态**

应用程序组件可以通过注入接口`ApplicationAvailability`并调用其方法来随时检索当前的可用性状态。更常见的是，应用程序希望监听状态更新或更新应用程序的状态。

例如，我们可以将应用程序的“Readiness”状态导出到一个文件，以便 Kubernetes“exec Probe”可以查看该文件：

```
@Component
public class MyReadinessStateExporter {
​
    @EventListener
    public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
        switch (event.getState()) {
            case ACCEPTING_TRAFFIC -> {
                // create file /tmp/healthy
            }
            case REFUSING_TRAFFIC -> {
                // remove file /tmp/healthy
            }
        }
    }
​
}
```

当应用程序中断且无法恢复时，我们还可以更新应用程序的状态：

```
@Component
public class MyLocalCacheVerifier {
​
    private final ApplicationEventPublisher eventPublisher;
​
    public MyLocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
​
    public void checkLocalCache() {
        try {
            // ...
        }
        catch (CacheCompletelyBrokenException ex) {
            AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
        }
    }
​
}
```

Spring Boot 通过 Actuator Health Endpoints 提供[Kubernetes HTTP 探测“Liveness”和“Readiness”](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.endpoints.kubernetes-probes)。[您可以在专门部分中](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#deployment.cloud.kubernetes)获得有关在 Kubernetes 上部署 Spring Boot 应用程序的更多指导。

**7.1.7. 应用程序事件和侦听器**

除了常见的 Spring 框架事件（例如 ）之外，例如[`ContextRefreshedEvent`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/context/event/ContextRefreshedEvent.html)，`SpringApplication`还会发送一些其他应用程序事件。

> 有些事件实际上是在创建`ApplicationContext`之前触发的，因此您无法将这些事件的侦听器注册为`@Bean`. 您可以使用`SpringApplication.addListeners(…)`方法或`SpringApplicationBuilder.listeners(…)`方法来注册它们。如果您希望自动注册这些侦听器，无论应用程序的创建方式如何，您都可以将文件添加`META-INF/spring.factories`到项目中并使用`org.springframework.context.ApplicationListener` 这个key 引用您的侦听器，如以下示例所示：`org.springframework.context.ApplicationListener=com.example.project.MyListener`

当您的应用程序运行时，应用程序事件按以下顺序发送：

1. 在运行开始时但在任何处理之前发送`ApplicationStartingEvent`，监听器和初始化程序的注册除外。
2. 当已知上下文中要使用的`Environment` 时但在创建上下文之前，会发送`ApplicationEnvironmentPreparedEvent` 。
3. 当`ApplicationContext`准备好并且调用 ApplicationContextInitializers 时但在加载任何 bean 定义之前发送`ApplicationContextInitializedEvent` 。
4. 在上下文刷新开始之前、bean 定义加载之后发送`ApplicationPreparedEvent`。
5. 在刷新上下文之后但在调用任何应用程序和命令行运行程序之前发送`ApplicationStartedEvent` 。
6. 在 `LivenessState.CORRECT`之后立即发送`AvailabilityChangeEvent`，表示该应用程序被视为有效。
7. 在调用任何[应用程序和命令行运行程序](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.command-line-runner)后发送`ApplicationReadyEvent` 。
8. 紧随 `ReadinessState.ACCEPTING_TRAFFIC` 之后发送`AvailabilityChangeEvent`，表示应用程序已准备好处理请求。
9. 如果启动时出现异常，则会发送一个`ApplicationFailedEvent`。

上面的列表仅包含与`SpringApplication` 绑定的 `SpringApplicationEvent`s 。除此之外，以下事件也在`ApplicationStartedEvent` 之前和`ApplicationPreparedEvent`之后发布：

* `WebServer`就绪之后后发送`WebServerInitializedEvent`。 `ServletWebServerInitializedEvent`和`ReactiveWebServerInitializedEvent`分别对应 servlet 和响应式变体的发送事件。
* 当`ApplicationContext`刷新时发送`ContextRefreshedEvent`。

> 您通常不需要使用应用程序事件，但知道它们的存在会很方便。在内部，Spring Boot 使用事件来处理各种任务。
>
> 事件侦听器不应运行可能很长的任务，因为默认情况下它们在同一线程中执行。考虑使用[应用程序和命令行运行程序](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.command-line-runner)。

应用程序事件是通过使用Spring框架的事件发布机制来发送的。此机制的一部分确保发布到子上下文中的侦听器的事件也发布到任何祖先上下文中的侦听器。因此，如果您的应用程序使用`SpringApplication`实例层次结构，侦听器可能会接收同一类型应用程序事件的多个实例。

为了让您的侦听器区分其上下文的事件和后代上下文的事件，它应该请求注入其应用程序上下文，然后将注入的上下文与事件的上下文进行比较。可以通过实现注入上下文`ApplicationContextAware`，或者如果侦听器是 bean，则可以使用`@Autowired`.

**7.1.8. 网络环境**

`SpringApplication`代表您尝试创建正确的类型。用于确定 `ApplicationContext` 的`WebApplicationType`算法如下：

* 如果存在 Spring MVC，则使用`AnnotationConfigServletWebServerApplicationContext`
* 如果 Spring MVC 不存在并且 Spring WebFlux 存在，则使用`AnnotationConfigReactiveWebServerApplicationContext`
* 否则，使用`AnnotationConfigApplicationContext`

这意味着，如果您在同一应用程序中使用 Spring MVC 和 Spring WebFlux 中的`WebClient`新功能，则默认情况下将使用 Spring MVC。您可以通过调用`setWebApplicationType(WebApplicationType)`轻松覆盖它。

还可以通过调用`setApplicationContextFactory(…)`来完全控制`ApplicationContext`所使用的类型。

> 在 JUnit 测试中使用`SpringApplication`时通常需要调用`setWebApplicationType(WebApplicationType.NONE)`。

**7.1.9. 访问应用程序参数**

如果您需要访问传递给`SpringApplication.run(…)` 的应用程序参数，您可以注入一个`org.springframework.boot.ApplicationArguments`bean。`ApplicationArguments`接口提供对原始`String[]`参数以及解析后的`option`和`non-option`参数的访问，如以下示例所示：

```
@Component
public class MyBean {
​
    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();
        if (debug) {
            System.out.println(files);
        }
        // if run with "--debug logfile.txt" prints ["logfile.txt"]
    }
​
}
```

> Spring Boot 还向 Spring `Environment`注册了一个`CommandLinePropertySource`. 这使您还可以使用`@Value`注释注入单个应用程序参数。

**7.1.10. 使用 ApplicationRunner 或 CommandLineRunner**

如果您需要在`SpringApplication`启动后运行某些特定代码，您可以实现`ApplicationRunner`或`CommandLineRunner`接口。两个接口以相同的方式工作，并提供一个`run`方法，该方法在`SpringApplication.run(…)`完成之前调用。

> 该合约非常适合应在应用程序启动后但开始接受流量之前运行的任务。

这些`CommandLineRunner`接口以字符串数组的形式提供对应用程序参数的访问，而 `ApplicationRunner`则使用前面讨论的`ApplicationArguments`接口。以下示例显示了`CommandLineRunner`的`run`方法：

爪哇

科特林

```
@Component
public class MyCommandLineRunner implements CommandLineRunner {
​
    @Override
    public void run(String... args) {
        // Do something...
    }
​
}
```

如果定义了多个`CommandLineRunner`或`ApplicationRunner`必须按特定顺序调用的 bean，则可以另外实现该`org.springframework.core.Ordered`接口或使用`org.springframework.core.annotation.Order`注释。

**7.1.11. 应用程序退出**

每个进程`SpringApplication`都会向 JVM 注册一个关闭钩子，以确保`ApplicationContext`在退出时正常关闭。可以使用所有标准 Spring 生命周期回调（例如`DisposableBean`接口或`@PreDestroy`注释）。

此外，如果希望在`SpringApplication.exit()`调用时返回特定的退出代码，则bean 可以实现`org.springframework.boot.ExitCodeGenerator`接口。然后可以传递此退出代码`System.exit()`以将其作为状态代码返回，如以下示例所示：

```
@SpringBootApplication
public class MyApplication {
​
    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
        return () -> 42;
    }
​
    public static void main(String[] args) {
        System.exit(SpringApplication.exit(SpringApplication.run(MyApplication.class, args)));
    }
​
}
```

此外，该`ExitCodeGenerator`接口可以通过异常来实现。当遇到此类异常时，Spring Boot 将返回所实现的`getExitCode()`方法提供的退出代码。

如果有多个`ExitCodeGenerator`，则使用生成的第一个非零退出代码。要控制生成器的调用顺序，请另外实现接口`org.springframework.core.Ordered`或使用`org.springframework.core.annotation.Order`注释。

**7.1.12. 管理功能**

可以通过指定属性来启用应用程序的管理相关功能`spring.application.admin.enabled`。这暴露了[`SpringApplicationAdminMXBean`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java)平台上的情况`MBeanServer`。您可以使用此功能远程管理您的 Spring Boot 应用程序。此功能对于任何服务包装器实现也很有用。

> 如果您想知道应用程序在哪个 HTTP 端口上运行，请获取键为`local.server.port` 的属性。

**7.1.13. 应用程序启动跟踪**

在应用程序启动期间，`SpringApplication`和`ApplicationContext`将执行许多与应用程序生命周期、bean 生命周期甚至处理应用程序事件相关的任务。通过[`ApplicationStartup`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/core/metrics/ApplicationStartup.html)， Spring Framework允许您可以使用[`StartupStep`对象](https://docs.spring.io/spring-framework/reference/6.1/core/beans/context-introduction.html#context-functionality-startup)[跟踪应用程序启动顺序](https://docs.spring.io/spring-framework/reference/6.1/core/beans/context-introduction.html#context-functionality-startup)。收集这些数据可以用于分析目的，或者只是为了更好地了解应用程序启动过程。

可以在设置`SpringApplication`实例时选择实现`ApplicationStartup`您。例如，要使用`BufferingApplicationStartup`，您可以编写：

```
@SpringBootApplication
public class MyApplication {
​
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setApplicationStartup(new BufferingApplicationStartup(2048));
        application.run(args);
    }
​
}
```

第一个可用的实现`FlightRecorderApplicationStartup`是由 Spring 框架提供的。它将 Spring 特定的启动事件添加到 Java Flight Recorder 会话中，旨在分析应用程序并将其 Spring 上下文生命周期与 JVM 事件（例如分配、GC、类加载……）相关联。配置完成后，您可以通过在启用飞行记录器的情况下运行应用程序来记录数据：

```
$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar
```

Spring Boot 附带了该`BufferingApplicationStartup`变体；此实现旨在缓冲启动步骤并将其排入外部指标系统。应用程序可以请求`BufferingApplicationStartup`任何组件中类型的 bean。

Spring Boot 还可以配置为公开一个[`startup`端点](https://docs.spring.io/spring-boot/docs/3.2.0/actuator-api/htmlsingle/#startup)，该端点以 JSON 文档的形式提供此信息。

**7.1.14. 虚拟线程**

如果您在 Java 21 或更高版本上运行，则可以通过将该属性设置`spring.threads.virtual.enabled`为`true` 来启用虚拟线程。

> 虚拟线程的一个副作用是这些线程是守护线程。如果没有非守护线程，JVM 将退出。当您依赖（例如）`@Scheduled`bean 来保持应用程序处于活动状态时，此行为可能会成为问题。如果您使用虚拟线程，则调度程序线程是虚拟线程，因此是守护程序线程，并且不会使 JVM 保持活动状态。这不仅会影响调度，其他技术也可能出现这种情况！为了使 JVM 在所有情况下都保持运行，建议将该属性设置`spring.main.keep-alive`为`true`。这可以确保 JVM 保持活动状态，即使所有线程都是虚拟线程。


# 7.2. 外部化配置

Spring Boot 允许您外部化您的配置，以便您可以在不同的环境中使用相同的应用程序代码。您可以使用各种外部配置源，包括 Java 属性文件、YAML 文件、环境变量和命令行参数。

属性值可以使用`@Value`注释直接注入到bean中，通过Spring的`Environment`抽象访问，或者通过`@ConfigurationProperties`绑定到结构化对象。

Spring Boot 使用一种非常特殊的`PropertySource`顺序，旨在允许合理地覆盖值。后面的属性源可以覆盖前面定义的值。按以下顺序考虑来源：

1. 默认属性（由设置`SpringApplication.setDefaultProperties`指定）。
2. `@Configuration`类上的[`@PropertySource`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/context/annotation/PropertySource.html)注释。请注意，在刷新应用程序上下文之前，不会将此类属性源添加到`Environment`中。现在配置某些属性为时已晚，例如在刷新开始之前读取的`logging.*`和`spring.main.*`。
3. 配置数据（例如`application.properties`文件）。
4. 仅在 `random.*`中具有的`RandomValuePropertySource`属性。
5. 操作系统环境变量。
6. Java 系统属性 ( `System.getProperties()`)。
7. 来自`java:comp/env`的JNDI 属性.
8. `ServletContext`初始化参数。
9. `ServletConfig`初始化参数。
10. 来自`SPRING_APPLICATION_JSON`（嵌入环境变量或系统属性中的内联 JSON）的属性。
11. 命令行参数。
12. 测试的`properties`属性。可用于[`@SpringBootTest`](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/test/context/SpringBootTest.html)和[测试应用程序的特定部分的测试注释](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.spring-boot-applications.autoconfigured-tests)。
13. 测试中的[`@DynamicPropertySource`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/test/context/DynamicPropertySource.html)注释。
14. 测试中的[`@TestPropertySource`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/test/context/TestPropertySource.html)注释。
15. 当 devtools 处于活动状态时`$HOME/.config/spring-boot`目录中的Devtools[全局设置属性。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools.globalsettings)

配置数据文件按以下顺序考虑：

1. 打包在 jar 内的[应用程序属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files)（`application.properties`和 YAML 变体）。
2. 打包在 jar（`application-{profile}.properties`和 YAML 变体）内的[特定于配置文件的应用程序属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.profile-specific)。
3. 打包的 jar（`application.properties`和 YAML 变体）之外的[应用程序属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files)。
4. 打包的 jar（`application-{profile}.properties`和 YAML 变体）之外的[特定于配置文件的应用程序属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.profile-specific)。

> 建议您的整个申请坚持使用一种格式。如果您在同一位置同时具有`.properties`和 YAML 格式的配置文件，则`.properties`优先。
>
> 如果您使用环境变量而不是系统属性，则大多数操作系统不允许使用句点分隔的键名称，但您可以使用下划线代替（例如，`SPRING_CONFIG_NAME`代替`spring.config.name`）。有关详细信息， 请参阅[从环境变量绑定。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables)
>
> 如果您的应用程序在 servlet 容器或应用程序服务器中运行，则可以使用 JNDI 属性（在`java:comp/env` 中）或 servlet 上下文初始化参数来代替环境变量或系统属性，或者与环境变量或系统属性一起使用。

为了提供一个具体的示例，假设您开发一个`@Component`使用`name`属性的应用程序，如以下示例所示：

```
@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}
```

在应用程序类路径上（例如，在 jar 内），您可以有一个 `application.properties` 文件，它为`name`提供合理的默认属性值。在新环境中运行时，可以在 jar 外部提供`application.properties` 文件来覆盖`name`。对于一次性测试，您可以使用特定的命令行开关启动（例如`java -jar app.jar --name="Spring"`）。

> `env`和`configprops`端点可用于确定属性具有特定值的原因。您可以使用这两个端点来诊断意外的属性值。有关详细信息，请参阅“[生产就绪功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.endpoints)”部分。

**7.2.1. 访问命令行属性**

默认情况下，`SpringApplication`将任何命令行选项参数（即以 `--`开头的参数，例如`--server.port=9000`）转换为`property`并将它们添加到 Spring `Environment`。如前所述，命令行属性始终优先于基于文件的属性源。

如果您不希望将命令行属性添加到`Environment` 中，可以使用 `SpringApplication.setAddCommandLineProperties(false)`禁用它们。

**7.2.2. JSON 应用程序属性**

环境变量和系统属性通常有限制，这意味着某些属性名称无法使用。为了解决这个问题，Spring Boot 允许您将属性块编码到单个 JSON 结构中。

当您的应用程序启动时，任何`spring.application.json`或`SPRING_APPLICATION_JSON`属性都将被解析并添加到`Environment`.

例如，可以在 UN\*X shell 的命令行上将`SPRING_APPLICATION_JSON`属性作为环境变量提供：

```
$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar
```

在前面的示例中，`my.name=test`最终会进入Spring `Environment`。

还可以提供相同的 JSON 作为系统属性：

```
$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar
```

或者您可以使用命令行参数提供 JSON：

```
$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'
```

如果要部署到经典应用程序服务器，您还可以使用名为`java:comp/env/spring.application.json` 的 JNDI 变量。

尽管JSON 中的`null`值将添加到生成的属性源中，但`PropertySourcesPropertyResolver`会将`null`属性视为缺失值。这意味着 JSON 无法使用`null`值覆盖来自低阶属性源的属性。

**7.2.3. 外部应用程序属性**

当您的应用程序启动时，Spring Boot 将自动从以下位置查找并加载`application.properties`文件：`application.yaml`

1. 从类路径
   1. 类路径根
   2. 类路径`/config`包
2. 从当前目录
   1. 当前目录
   2. `config/`当前目录下的子目录
   3. `config/`子目录的直接子目录

该列表按优先级排序（较低项目的值覆盖较早项目的值）。已加载文件中的文档将作为`PropertySources`添加到 Spring `Environment`中。

如果您不喜欢`application` 作为配置文件名，可以通过指定 `spring.config.name`环境属性切换到其他文件名。例如，要查找 `myproject.properties` 和 `myproject.yaml` 文件，您可以按如下方式运行应用程序：

```
$ java -jar myproject.jar --spring.config.name=myproject
```

您还可以使用`spring.config.location`环境属性来引用显式位置。此属性接受以逗号分隔的一个或多个要检查位置的列表。

以下示例显示如何指定两个不同的文件：

```
$ java -jar myproject.jar --spring.config.location=\
    optional:classpath:/default.properties,\
    optional:classpath:/override.properties
```

> 如果[位置是可选的](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.optional-prefix)并且您不介意它们不存在， 请使用`optional:`前缀。
>
> `spring.config.name`、`spring.config.location`、 和`spring.config.additional-location`很早就被用来确定必须加载哪些文件。它们必须定义为环境属性（通常是操作系统环境变量、系统属性或命令行参数）。

如果`spring.config.location`包含目录（而不是文件），则它们应该以`/`结束。在运行时，它们将加载之前附加上从`spring.config.name`生成的名称。`spring.config.location`中指定的文件将直接导入。

目录和文件位置值也会扩展以检查[特定于配置文件的文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.profile-specific)。例如，如果您有一个`spring.config.location` 的 `classpath:myconfig.properties`，您还会发现加载了相应的`classpath:myconfig-.properties`文件。

在大多数情况下，您添加的每个`spring.config.location`项目都将引用单个文件或目录。位置按照定义的顺序进行处理，后面的位置可以覆盖前面位置的值。

如果您有复杂的位置设置，并且使用特定于配置文件的配置文件，则可能需要提供进一步的提示，以便 Spring Boot 知道应如何对它们进行分组。位置组是所有被视为同一级别的位置的集合。例如，您可能想要对所有类路径位置进行分组，然后对所有外部位置进行分组。位置组内的项目应使用 分隔`;`。有关更多详细信息，请参阅“[配置文件特定文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.profile-specific)”部分中的示例。

使用`spring.config.location`配置的位置替换默认位置。例如，如果`spring.config.location`配置了值`optional:classpath:/custom-config/,optional:file:./custom-config/`，则考虑的完整位置集为：

1. `optional:classpath:custom-config/`
2. `optional:file:./custom-config/`

如果您希望添加其他位置而不是替换它们，则可以使用`spring.config.additional-location`. 从其他位置加载的属性可以覆盖默认位置中的属性。例如，如果`spring.config.additional-location`配置了值`optional:classpath:/custom-config/,optional:file:./custom-config/`，则考虑的完整位置集为：

1. `optional:classpath:/;optional:classpath:/config/`
2. `optional:file:./;optional:file:./config/;optional:file:./config/*/`
3. `optional:classpath:custom-config/`
4. `optional:file:./custom-config/`

通过这种搜索顺序，您可以在一个配置文件中指定默认值，然后有选择地覆盖另一个配置文件中的这些值。您可以在默认位置之一的 `application.properties`（或您使用 `spring.config.name`选择的任何其他基本名称）中为应用程序提供默认值。然后可以在运行时使用位于自定义位置之一的不同文件覆盖这些默认值。

**Optional Locations**

默认情况下，当指定的配置数据位置不存在时，Spring Boot 将抛出`ConfigDataLocationNotFoundException`异常，并且您的应用程序将无法启动。

如果您想指定一个位置，但不介意它并不总是存在，则可以使用`optional:`前缀。您可以将此前缀与`spring.config.location`和`spring.config.additional-location`属性以及[`spring.config.import`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.importing)声明一起使用。

例如，即使`myconfig.properties`文件丢失，`optional:file:./myconfig.properties`中的`spring.config.import`值也允许您的应用程序启动。

如果您想忽略所有`ConfigDataLocationNotFoundExceptions`并始终继续启动您的应用程序，您可以使用该`spring.config.on-not-found`属性。使用`SpringApplication.setDefaultProperties(…)`将值设置为`ignore`或与系统/环境变量一起使用。

**通配符位置**

如果配置文件位置包含最后一个路径段的`*`字符，则它被视为通配符位置。加载配置时会扩展通配符，以便还会检查直接子目录。当存在多个配置属性源时，通配符位置在 Kubernetes 等环境中特别有用。

例如，如果您有一些 Redis 配置和一些 MySQL 配置，您可能希望将这两个配置分开，同时要求这两个配置都存在于一个文件中`application.properties`。这可能会导致两个单独的`application.properties`文件安装在不同的位置，例如`/config/redis/application.properties`和`/config/mysql/application.properties`。在这种情况下，通配符位置`config/*/`, 将导致两个文件都被处理。

默认情况下，Spring Boot 在默认搜索位置中包含`config/*/`。这意味着将搜索 jar 之外的`/config`目录的所有子目录。

您可以自行将通配符位置与`spring.config.location`和`spring.config.additional-location`属性一起使用。

> 通配符位置必须仅包含一个`*`，并以 `*/`结尾（对于目录搜索位置）或以`*/<filename>` 结尾（对于文件搜索位置）。带有通配符的位置根据文件名的绝对路径按字母顺序排序。
>
> 通配符位置仅适用于外部目录。您不能在`classpath:`位置使用通配符。

**配置文件特定文件**

除了`application`属性文件之外，Spring Boot 还将尝试使用`application-{profile}`命名约定加载特定于配置文件的文件。例如，如果您的应用程序激活名为`prod`并使用 YAML 文件的配置文件，则 `application.yaml`和`application-prod.yaml`都会被考虑。

配置文件特定的属性从与标准`application.properties`相同的位置加载，配置文件特定的文件始终覆盖非特定的文件。如果指定了多个配置文件，则应用最后获胜策略。例如，如果由`spring.profiles.active`属性指定了配置文件`prod,live`，则`application-prod.properties` 中的值可以被 `application-live.properties`中的值覆盖。

> 最后获胜策略适用于位置组级别。 `spring.config.location` 的`classpath:/cfg/,classpath:/ext/`不会具有与 `classpath:/cfg/;classpath:/ext/` 相同的覆盖规则。
>
> 例如，继续上面的 prod,live 示例，我们可能有以下文件：
>
> ```
> /cfg
> application-live.properties
> /ext
> application-live.properties
> application-prod.properties
> ```
>
> 当我们有一个`spring.config.location` 的`classpath:/cfg/,classpath:/ext/` 时，我们会在所有`/ext` 文件之前处理所有`/cfg`文件：
>
> 1. `/cfg/application-live.properties`
> 2. `/ext/application-prod.properties`
> 3. `/ext/application-live.properties`
>
> 当我们使用 `classpath:/cfg/;classpath:/ext/`（带有`;`分隔符）时，我们会在同一级别处理`/cfg`和`/ext`：
>
> 1. `/ext/application-prod.properties`
> 2. `/cfg/application-live.properties`
> 3. `/ext/application-live.properties`

`Environment`有一组默认配置文件（默认情况下为`[default]`），如果未设置`active`配置文件，则使用这些配置文件。换句话说，如果没有显式激活配置文件，则考虑来自`application-default`的属性。

> 属性文件仅加载一次。如果您已经直接[导入了](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.importing)配置文件特定的属性文件，则不会再次导入。

**导入附加数据**

应用程序属性可以使用`spring.config.import`属性从其他位置导入更多配置数据。进口在发现时进行处理，并被视为紧邻插入进口申报文件下方的附加文件。

例如，您的类路径`application.properties`文件中可能包含以下内容：

```
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
```

这将触发当前目录中`dev.properties`文件的导入（如果存在这样的文件）。`dev.properties`导入的值将优先于触发导入的文件。在上面的示例中，`dev.properties`可以重新定义`spring.application.name`为不同的值。

一次导入无论声明多少次，都只会导入一次。在properties/yaml 文件中的单个文档中定义导入的顺序并不重要。例如，下面的两个示例产生相同的结果：

```
spring.config.import=my.properties
my.property=value
```

```
my.property=value
spring.config.import=my.properties
```

在上述两个示例中，文件中的值`my.properties`将优先于触发其导入的文件。

可以在一个键下指定多个位置`spring.config.import`。位置将按照定义的顺序进行处理，稍后导入的优先。

> 在适当的情况下，还会考虑导入[特定于配置文件的变体。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.profile-specific)上面的示例将导`my.properties`以及任何`my-<profile>.properties`变体。
>
> Spring Boot 包含可插入 API，允许支持各种不同的位置地址。默认情况下，您可以导入 Java Properties、YAML 和“配置树”。
>
> 第三方 jar 可以提供对其他技术的支持（不要求文件位于本地）。例如，您可以想象配置数据来自外部存储，例如 `Consul、Apache ZooKeeper`或 `Netflix Archaius`。
>
> 如果您想支持自己的位置，请参阅 `org.springframework.boot.context.config` 包中的`ConfigDataLocationResolver` 和 `ConfigDataLoader`类。

**导入无扩展名文件**

某些云平台无法为卷安装的文件添加文件扩展名。要导入这些无扩展名文件，您需要给 Spring Boot 一个提示，以便它知道如何加载它们。您可以通过将扩展提示放在方括号中来完成此操作。

例如，假设您有一个`/etc/config/myconfig`文件希望导入为 yaml。您可以在`application.properties`使用以下命令将其导入：

```
spring.config.import=file:/etc/config/myconfig[.yaml]
```

**使用配置树**

在云平台（例如 Kubernetes）上运行应用程序时，您通常需要读取平台提供的配置值。将环境变量用于此类目的并不罕见，但这可能有缺点，特别是如果该值应该保密的话。

作为环境变量的替代方案，许多云平台现在允许您将配置映射到已安装的数据卷中。例如，Kubernetes 可以对[`ConfigMaps`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap)和[`Secrets`](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod)进行卷挂载。

可以使用两种常见的卷安装模式：

1. 单个文件包含一组完整的属性（通常编写为 YAML）。
2. 多个文件被写入目录树，文件名成为“键”，内容成为“值”。

[对于第一种情况，您可以使用上述](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files.importing)`spring.config.import`方法直接导入 YAML 或 Properties 文件。对于第二种情况，您需要使用`configtree:`前缀，以便 Spring Boot 知道它需要将所有文件公开为属性。

作为示例，我们假设 Kubernetes 已安装以下卷：

```
etc/
  config/
    myapp/
      username
      password
```

文件的内容`username`将是一个配置值，并且 `password`的内容将是一个加密内容。

要导入这些属性，您可以将以下内容添加到您的`application.properties`或`application.yaml`文件中：

```
spring.config.import=optional:configtree:/etc/config/
```

然后，您可以按照通常的方式从环境中访问或注入 `myapp.username`和`myapp.password` 属性。

> 配置树下的文件夹和文件的名称构成属性名称。在上面的示例中，要访问用户名和密码属性，您可以将`spring.config.import` 设置为`optional:configtree:/etc/config/myapp`。
>
> 带点符号的文件名也可以正确映射。例如，在上面的示例中，`/etc/config` 中名为`myapp.username` 的文件将在环境中生成 `myapp.username`属性。
>
> 配置树值可以根据预期的内容 绑定到字符串`String`和`byte[]`类型。

如果您有多个配置树要从同一父文件夹导入，则可以使用通配符快捷方式。任何`configtree:`以`/*/` 结尾的位置都将导入所有直接子项作为配置树。与非通配符导入一样，每个配置树下的文件夹和文件的名称形成属性名称。

例如，给定以下卷：

```
etc/
  config/
    dbconfig/
      db/
        username
        password
    mqconfig/
      mq/
        username
        password
```

您可以用作`configtree:/etc/config/*/`导入位置：

```
spring.config.import=optional:configtree:/etc/config/*/
```

这将添加`db.username`、`db.password`、`mq.username`和`mq.password`属性。

> 使用通配符加载的目录按字母顺序排序。如果您需要不同的订单，那么您应该将每个位置列为单独的导入

配置树也可用于 Docker 秘密。当 Docker swarm 服务被授予对机密的访问权限时，该机密就会被安装到容器中。例如，如果将名为`db.password` 的机密安装在 `/run/secrets/`，您可以使用以下命令将`db.password`提供给 Spring 环境：

```
spring.config.import=optional:configtree:/run/secrets/
```

**属性占位符**

`application.properties`和`application.yaml`中的值在使用时会通过`Environment`现有值进行过滤，因此您可以引用以前定义的值（例如，来自系统属性或环境变量的值）。标准`${name}`属性占位符语法可以在值内的任何位置使用。属性占位符还可以使用 `:`将默认值与属性名称分隔开来指定默认值，例如`${name:default}`。

以下示例显示了带默认值和不带默认值的占位符的使用：

```
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
```

假设`username`属性尚未在其他地方设置，`app.description`则值为`MyApp is a Spring Boot application written by Unknown`。

> 您应该始终使用规范形式（仅使用小写字母的短横线大小写）来引用占位符中的属性名称。这将允许 Spring Boot 使用与宽松绑定 @ConfigurationProperties 时相同的逻辑。
>
> 例如，`${demo.item-price}`将从`application.properties`文件中获取`demo.item-price`和`demo.itemPrice`表单，以及从系统环境中获取`EMO_ITEMPRICE`。如果您使用 `${demo.itemPrice}`代替，则不会考虑`demo.item-price`和 `DEMO_ITEMPRICE`。
>
> 您还可以使用此技术创建现有 Spring Boot 属性的“短”变体。有关详细信息，请参阅[*使用“短”命令行参数操作*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.properties-and-configuration.short-command-line-arguments)方法。

**处理多文档文件**

Spring Boot 允许您将单个物理文件拆分为多个逻辑文档，每个逻辑文档都是独立添加的。文件按从上到下的顺序处理。后面的文档可以覆盖前面文档中定义的属性。

对于`application.yaml`文件，使用标准 YAML 多文档语法。三个连续的连字符代表一个文档的结束和下一个文档的开始。

例如，以下文件有两个逻辑文档：

```
spring:
  application:
    name: "MyApp"
---
spring:
  application:
    name: "MyCloudApp"
  config:
    activate:
      on-cloud-platform: "kubernetes"
```

对于`application.properties`文件，使用特殊`#---`或`!---`注释来标记文档拆分：

```
spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
```

> 属性文件分隔符不得包含任何前导空格，并且必须恰好包含三个连字符。分隔符之前和之后的行不能是相同的注释前缀。
>
> 多文档属性文件通常与 spring.config.activate.on-profile 等激活属性结合使用。有关详细信息，请参阅下一节。
>
> 无法使用`@PropertySource`或`@TestPropertySource`注释加载多文档属性文件。

**激活属性文件**

有时，仅在满足某些条件时激活一组给定的属性很有用。例如，您可能拥有仅在特定配置文件处于活动状态时才相关的属性。

您可以使用 有条件地激活属性文档`spring.config.activate.*`。

以下激活属性可用：

| 属性                  | 备注                              |
| ------------------- | ------------------------------- |
| `on-profile`        | 必须匹配才能使文档处于活动状态的配置文件表达式。        |
| `on-cloud-platform` | `CloudPlatform`必须检测到文档才能处于活动状态。 |

例如，以下内容指定第二个文档仅在 Kubernetes 上运行时才处于活动状态，并且仅当“prod”或“staging”配置文件处于活动状态时：

```
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
```

**7.2.4. 加密属性**

Spring Boot 不提供任何对加密属性值的内置支持，但是，它确实提供了修改 Spring `Environment`中包含的值所需的挂钩点。`EnvironmentPostProcessor`接口允许您在应用程序启动之前操作`Environment`。有关详细信息，请参阅[在启动之前自定义环境或 ApplicationContext 。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.application.customize-the-environment-or-application-context)

如果您需要一种安全的方式来存储凭据和密码，[Spring Cloud Vault项目提供了在](https://cloud.spring.io/spring-cloud-vault/)[HashiCorp Vault](https://www.vaultproject.io/)中存储外部化配置的支持。

**7.2.5. 使用 YAML**

[YAML](https://yaml.org/)是 JSON 的超集，因此是指定分层配置数据的便捷格式。[只要您的类路径上有SnakeYAML](https://github.com/snakeyaml/snakeyaml)库，`SpringApplication`就会自动支持 YAML 作为属性的替代方案。

> 如果您使用“Starters”，SnakeYAML 会自动由`spring-boot-starter`提供.

**将 YAML 映射到属性文件**

YAML 文档需要从分层格式转换为可与 Spring 一起使用的平面结构`Environment`。例如，考虑以下 YAML 文档：

```
environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"
```

为了从`Environment` 访问这些属性，它们将被展平如下：

```
environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App
```

同样，YAML 列表也需要扁平化。它们表示为具有`[index]`解引用器的属性键。例如，考虑以下 YAML：

```
my:
 servers:
 - "dev.example.com"
 - "another.example.com"
```

前面的示例将转换为以下属性：

```
my.servers[0]=dev.example.com
my.servers[1]=another.example.com
```

> 使用 \[index] 表示法的属性可以使用 Spring Boot 的 Binder 类绑定到 `Java List` 或`Set`对象。有关更多详细信息，请参阅下面的“类型安全配置属性”部分。
>
> 无法使用`@PropertySource`或`@TestPropertySource`注释 加载 YAML 文件。因此，如果您需要以这种方式加载值，则需要使用属性文件。

**直接加载YAML**

Spring框架提供了两个方便的类，可用于加载YAML文档。`YamlPropertiesFactoryBean`将YAML 加载为`Properties`，`YamlMapFactoryBean`将YAML 加载为`Map`.

如果您想将 YAML 作为 Spring `PropertySource`加载，您也可以使用`YamlPropertySourceLoader`类。

**7.2.6. 配置随机值**

`RandomValuePropertySource`对于注入随机值（例如，注入秘密或测试用例）非常有用。它可以生成整数、长整型、uuid 或字符串，如以下示例所示：

```
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
```

`random.int*` 语法是 `OPEN value (,max) CLOSE`，其中`OPEN、CLOSE`是任意字符，`value、max`是整数。如果提供了`max`，则 `value`是最小值，`max` 是最大值（不包括）。

**7.2.7. 配置系统环境属性**

Spring Boot 支持为环境属性设置前缀。如果系统环境由具有不同配置要求的多个 Spring Boot 应用程序共享，这非常有用。系统环境属性的前缀可以直接在 `SpringApplication`上设置。

例如，如果将前缀设置为`input`，则诸如`remote.timeout` 之类的属性也将像在系统环境中一样被作为`input.remote.timeout`解析。

**7.2.8. 类型安全的配置属性**

使用`@Value("${property}")`注释来注入配置属性有时可能很麻烦，特别是当您正在使用多个属性或您的数据本质上是分层的时。Spring Boot 提供了另一种使用属性的方法，让强类型 bean 管理和验证应用程序的配置。

另请参阅[类型安全配置属性和类型安全配置属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.vs-value-annotation)[之间的差异`@Value`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.vs-value-annotation)。

**JavaBean 属性绑定**

可以绑定声明标准 JavaBean 属性的 bean，如以下示例所示：

```
@ConfigurationProperties("my.service")
public class MyProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        // getters / setters...

    }

}
```

前面的 POJO 定义了以下属性：

* `my.service.enabled`，默认值为`false`。
* `my.service.remote-address`，具有可以从 `String`强转的类型。
* `my.service.security.username`，带有一个嵌套的“安全”对象，其名称由属性名称确定。特别是，该类型根本没有在那里使用，并且本来可以使用`SecurityProperties`。
* `my.service.security.password`。
* `my.service.security.roles`，其中的`String`集合默认为`USER`.

> 映射到 Spring Boot 中可用的`@ConfigurationProperties`类的属性（通过属性文件、YAML 文件、环境变量和其他机制进行配置）是公共 API，但类本身的访问器（getter/setter）并不意味着可以直接使用。
>
> 这种安排依赖于默认的空构造函数，并且 getter 和 setter 通常是强制性的，因为绑定是通过标准 Java Beans 属性描述符进行的，就像在 Spring MVC 中一样。在以下情况下可以省略 setter：
>
> * 集合，只要它们被初始化，就需要一个 getter，但不一定需要一个 setter，因为它们可以被绑定器改变。
> * 可以通过索引（通常使用 YAML）或使用单个逗号分隔值（属性）来访问的集合和数组。在后一种情况下，setter 是强制性的。我们建议始终为此类类型添加 setter。如果初始化集合，请确保它不是不可变的（如前面的示例所示）。
> * 如果嵌套 POJO 属性已初始化（如`Security`前面示例中的字段），则不需要 setter。
>
> 如果您希望绑定器使用其默认构造函数动态创建实例，则需要一个设置器。有些人使用 Project Lombok 自动添加 getter 和 setter。确保 Lombok 不会为此类类型生成任何特定的构造函数，因为容器会自动使用它来实例化对象。最后，仅考虑标准 Java Bean 属性，并且不支持静态属性的绑定。

**构造函数绑定**

上一节中的示例可以以不可变的方式重写，如下例所示：

```
@ConfigurationProperties("my.service")
public class MyProperties {

    // fields...

    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    // getters...

    public static class Security {

        // fields...

        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }

        // getters...

    }

}
```

在此设置中，单个参数化构造函数的存在意味着应使用构造函数绑定。这意味着绑定器将找到一个带有您希望绑定的参数的构造函数。如果您的类有多个构造函数，则可以使用`@ConstructorBinding`注释来指定用于构造函数绑定的构造函数。要选择退出具有单个参数化构造函数的类的构造函数绑定，该构造函数必须使用`@Autowired` 进行注释。构造函数绑定可以与记录一起使用。除非您的记录有多个构造函数，否则无需使用`@ConstructorBinding`.

构造函数绑定类的嵌套成员（例如`Security`上面的示例）也将通过其构造函数进行绑定。

可以使用构造函数的`@DefaultValue`参数和记录组件指定默认值。转换服务将用于将注释的`String`值强制转换为缺失属性的目标类型。

参考前面的示例，如果没有属性绑定到`Security`，则`MyProperties`实例将包含具有`null`值得`security`对象。要使`Security`包含非空实例，即使没有绑定任何属性（使用 Kotlin 时，这将要求`username`和`password`参数`Security`声明为可为空，因为它们没有默认值），请使用空注释`@DefaultValue`：

```
public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
    this.enabled = enabled;
    this.remoteAddress = remoteAddress;
    this.security = security;
}
```

> 要使用构造函数绑定，必须使用`@EnableConfigurationProperties`或配置属性扫描来启用类。您不能将构造函数绑定与由常规 Spring 机制创建的 bean（例如`@Component`bean、使用`@Bean`方法创建的 bean 或使用 `@Import`加载的 bean ）
>
> 要在本机映像中使用构造函数绑定，必须使用`-parameters`. 如果您使用 Spring Boot 的 Gradle 插件或者使用 Maven 和`spring-boot-starter-parent`.
>
> 不建议将 `java.util.Optional`与 `@ConfigurationProperties` 一起使用，因为它主要用作返回类型。因此，它不太适合配置属性注入。为了与其他类型的属性保持一致，如果您确实声明了Optional属性并且它没有值，则将绑定null而不是空Optional。

**启用@ConfigurationProperties注释的类型**

Spring Boot 提供了绑定`@ConfigurationProperties`类型并将它们注册为 bean 的基础设施。您可以逐类启用配置属性，也可以启用与组件扫描类似的配置属性扫描。

有时，带有`@ConfigurationProperties`注释的类可能不适合扫描，例如，如果您正在开发自己的自动配置或想要有条件地启用它们。在这些情况下，请使用`@EnableConfigurationProperties`注释指定要处理的类型列表。这可以在任何`@Configuration`类上完成，如以下示例所示：

```
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
```

```
@ConfigurationProperties("some.properties")
public class SomeProperties {

}
```

要使用配置属性扫描，请将`@ConfigurationPropertiesScan`注释添加到您的应用程序中。通常，它被添加到带有`@SpringBootApplication`注释的主应用程序类中，但它可以添加到任何`@Configuration`类中。默认情况下，将从声明注释的类的包中进行扫描。如果要定义要扫描的特定包，可以按照以下示例所示进行操作：

```
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
```

> 当使用配置属性扫描或通过`@EnableConfigurationProperties`注册`@ConfigurationProperties` bean时，该bean具有常规名称：`<prefix>-<fqn>`，其中`<prefix>`是`@ConfigurationProperties`注释中指定的环境键前缀，`<fqn>`是 bean 的完全限定名称。如果注释不提供任何前缀，则仅使用 bean 的完全限定名称。
>
> 假设它在`com.example.app`包中，上面的`SomeProperties`示例的bean名称是`some.properties-com.example.app.SomeProperties`。

我们建议`@ConfigurationProperties`只处理环境，特别是不要从上下文中注入其他 bean。对于极端情况，可以使用 setter 注入或框架提供的任何`*Aware`接口（例如`EnvironmentAware`，如果您需要访问`Environment`）。如果您仍然想使用构造函数注入其他 bean，则配置属性 bean 必须使用 `@Component`进行注释并使用基于 JavaBean 的属性绑定。

**使用@ConfigurationProperties注释的类型**

这种配置风格特别适用于`SpringApplication`的外部 YAML 配置，如以下示例所示：

```
my:
  service:
    remote-address: 192.168.1.1
    security:
      username: "admin"
      roles:
      - "USER"
      - "ADMIN"
```

要使用`@ConfigurationProperties`Bean，您可以按照与任何其他 Bean 相同的方式注入它们，如以下示例所示：

```
@Service
public class MyService {

    private final MyProperties properties;

    public MyService(MyProperties properties) {
        this.properties = properties;
    }

    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        server.start();
        // ...
    }

    // ...

}
```

> 使用`@ConfigurationProperties`还可以让您生成元数据文件，IDE 可以使用这些文件来为您自己的密钥提供自动完成功能。详细内容 请参见[附录。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.configuration-metadata)

**第三方配置**

`@ConfigurationProperties`除了用于注释类之外，您还可以在公共的`@Bean`方法上使用它。当您想要将属性绑定到您无法控制的第三方组件时，这样做特别有用。

要从`Environment`属性配置 bean，请添加`@ConfigurationProperties`到其 bean 注册中，如以下示例所示：

```
@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "another")
    public AnotherComponent anotherComponent() {
        return new AnotherComponent();
    }

}
```

使用`another` 前缀定义的任何 JavaBean 属性都会以与前面的 `SomeProperties` 示例类似的方式映射到 `AnotherComponent`bean。

**宽松的绑定**

Spring Boot 使用一些宽松的规则将`Environment`属性绑定到`@ConfigurationProperties`bean，因此`Environment`属性名称和 bean 属性名称之间不需要完全匹配。此功能有用的常见示例包括用破折号分隔的环境属性（例如，`context-path`绑定到`contextPath`）和大写的环境属性（例如，`PORT`绑定到`port`）。

作为示例，请考虑以下`@ConfigurationProperties`类：

```
@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {
​
    private String firstName;
​
    public String getFirstName() {
        return this.firstName;
    }
​
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
​
}
```

通过上面的代码，可以使用以下属性名称：

| 属性                                  | 备注                                       |
| ----------------------------------- | ---------------------------------------- |
| `my.main-project.person.first-name` | Kebab case，建议在`.properties`和 YAML 文件中使用。 |
| `my.main-project.person.firstName`  | 标准驼峰式语法。                                 |
| `my.main-project.person.first_name` | `.properties`下划线表示法，这是在YAML 文件中使用的替代格式。  |
| `MY_MAINPROJECT_PERSON_FIRSTNAME`   | 大写格式，在使用系统环境变量时建议使用大写格式。                 |

注释的`prefix`值必须*采用*短横线大小写（小写并用`-` 分隔，例如`my.main-project.person`）。

| 属性来源    | 单个命名                                                                                                                                                                                                 | 列表                                                                                                                                                                                              |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 属性文件    | 驼峰式大小写、烤肉串大小写或下划线表示法                                                                                                                                                                                 | 使用`[ ]`逗号分隔值的标准列表语法                                                                                                                                                                             |
| YAML 文件 | 驼峰式大小写、烤肉串大小写或下划线表示法                                                                                                                                                                                 | 标准 YAML 列表语法或逗号分隔值                                                                                                                                                                              |
| 环境变量    | 大写格式，下划线作为分隔符（请参阅[从环境变量绑定](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables)）。 | 用下划线包围的数值（请参阅[从环境变量绑定](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables)） |
| 系统属性    | 驼峰式大小写、烤肉串大小写或下划线表示法                                                                                                                                                                                 | 使用`[ ]`逗号分隔值的标准列表语法                                                                                                                                                                             |

我们建议，如果可能，属性以小写短横线格式存储，例如`my.person.first-name=Rod`.

**绑定Map**

绑定到`Map`属性时，您可能需要使用特殊的括号表示法，以便保留原始`key`值。如果键没有被 `[]`包围，则任何非字母数字字符`-`或`.`都会被删除。

例如，考虑将以下属性绑定到 `Map<String,String>`：

```
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
```

对于 YAML 文件，括号需要用引号括起来，以便正确解析键。

上面的属性将绑定到`Map`,`/key1`、`/key2`、`key3`作为映射中的键。斜杠已被删除，`key3`因为它没有被方括号包围。

绑定到标量值时，带有`.`其中不需要用`[]`包围。标量值包括枚举和 `java.lang`包中除 Object 之外的所有类型。将 `a.b=c`绑定到 Map 将保留 `.`并返回一个包含条目`{"a.b"="c"}`的 Map。对于任何其他类型，如果您的键包含`.`，则需要使用方括号表示法。例如，将 `a.b=c`绑定到 Map 将返回一个带有条目 `{"a"={"b"="c"}}` 的 Map 而`[a.b]=c` 将返回带有条目`{"a.b"="c"}`的 Map。

**从环境变量绑定**

大多数操作系统对可用于环境变量的名称施加严格的规则。例如，Linux shell 变量只能包含字母 ( `a`to`z`或`A`to `Z`)、数字 ( `0`to `9`) 或下划线字符 ( `_`)。按照惯例，Unix shell 变量的名称也采用大写形式。

Spring Boot 宽松的绑定规则旨在尽可能兼容这些命名限制。

要将规范形式的属性名称转换为环境变量名称，您可以遵循以下规则：

* 将点 ( `.`) 替换为下划线 ( `_`)。
* 删除所有破折号 ( `-`)。
* 转换为大写。

例如，配置属性`spring.main.log-startup-info`将是名为 `SPRING_MAIN_LOGSTARTUPINFO`的环境变量。

绑定到对象列表时也可以使用环境变量。要绑定到`List`，变量名称中的元素编号应用下划线括起来。

例如，配置属性`my.service[0].other`将使用名为 `MY_SERVICE_0_OTHER`的环境变量。

**缓存**

宽松绑定使用缓存来提高性能。默认情况下，此缓存仅应用于不可变属性源。要自定义此行为，例如启用可变属性源的缓存，请使用`ConfigurationPropertyCaching`.

**合并复杂类型**

当在多个位置配置列表时，覆盖通过替换整个列表来进行。

例如，假设一个`MyPojo`对象具有默认属性`null`的`name`和`description`。以下示例公开了来自 `MyPojo`的对象列表`MyProperties`：

```
@ConfigurationProperties("my")
public class MyProperties {
​
    private final List<MyPojo> list = new ArrayList<>();
​
    public List<MyPojo> getList() {
        return this.list;
    }
​
}
```

考虑以下配置：

```
my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
```

如果`dev`配置文件未激活，`MyProperties.list`则包含一个`MyPojo`条目（如先前所定义）。但是，如果`dev`配置文件已启用，则`list` *仍*仅包含一个条目（名称为`my another name`，描述为`null`）。此配置*不会*将第二个`MyPojo`实例添加到列表中，也不会合并项目。

当在多个配置文件中指定`List` 时，将使用具有最高优先级的配置文件（且仅使用该配置文件）。考虑以下示例：

```
my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
```

在前面的示例中，如果`dev`配置文件处于活动状态，`MyProperties.list`则包含*一个* `MyPojo`条目（名称为`my another name`，描述为`null`）。对于 YAML，逗号分隔列表和 YAML 列表都可用于完全覆盖列表的内容。

对于`Map`属性，您可以绑定从多个源获取的属性值。但是，对于多个源中的同一属性，将使用优先级最高的属性。以下示例公开了 `MyProperties`中的`Map<String, MyPojo>`：

```
@ConfigurationProperties("my")
public class MyProperties {
​
    private final Map<String, MyPojo> map = new LinkedHashMap<>();
​
    public Map<String, MyPojo> getMap() {
        return this.map;
    }
​
}
```

考虑以下配置：

```
my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
```

如果`dev`配置文件未激活，`MyProperties.map`则包含一个带有键`key1`的条目（名称为`my name 1`，描述为`my description 1`）。但是，如果`dev`配置文件已启用，则`map`包含两个带有键`key1`（名称为`dev name 1`，描述为`my description 1`）和`key2`（名称为`dev name 2`，描述为`dev description 2`）的条目。

> 前面的合并规则适用于所有属性源的属性，而不仅仅是文件。

**属性转换**

Spring Boot 在绑定到`@ConfigurationProperties`bean 时尝试将外部应用程序属性强制为正确的类型。如果您需要自定义类型转换，您可以提供一个`ConversionService` bean（带有名为 `conversionService` 的 bean）或自定义属性编辑器（通过`CustomEditorConfigurer`bean）或自定义`Converters`（带有注释为`@ConfigurationPropertiesBinding` 的 bean 定义）。

> 由于此 bean 在应用程序生命周期的早期就被请求，因此请确保限制您的`ConversionService`正在使用的依赖项。通常，您需要的任何依赖项在创建时可能不会完全初始化。如果配置键强制不需要并且仅依赖于 `@ConfigurationPropertiesBinding` 限定的自定义转换器，您可能需要重命名自定义`ConversionService`。

**转换持续时间**

Spring Boot 专门支持表达持续时间。如果您公开`java.time.Duration`属性，则应用程序属性中的以下格式可用：

* 正则`long`表示（除非指定了`@DurationUnit`，否则使用毫秒作为默认单位）
* [`java.time.Duration`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html#parse\(java.lang.CharSequence\))[使用的](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html#parse\(java.lang.CharSequence\))标准 ISO-8601 格式
* 一种更易读的格式，其中值和单位耦合（`10s`表示 10 秒）

考虑以下示例：

```
@ConfigurationProperties("my")
public class MyProperties {
​
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration sessionTimeout = Duration.ofSeconds(30);
​
    private Duration readTimeout = Duration.ofMillis(1000);
​
    // getters / setters...
​
}
```

要指定 30 秒的会话超时，`30`、`PT30S`和`30s`都是等效的。可以通过以下任意形式指定 500ms 的读取超时：`500`、`PT0.5S`和`500ms`。

您还可以使用任何受支持的单位。这些都是：

* `ns`纳秒
* `us`微秒
* `ms`毫秒
* `s`秒
* `m`分钟
* `h`小时
* `d`天

默认单位是毫秒，可以使用`@DurationUnit`上面示例中所示的方法进行覆盖。

如果您更喜欢使用构造函数绑定，则可以公开相同的属性，如以下示例所示：

```
@ConfigurationProperties("my")
public class MyProperties {
​
    // fields...
​
    public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
            @DefaultValue("1000ms") Duration readTimeout) {
        this.sessionTimeout = sessionTimeout;
        this.readTimeout = readTimeout;
    }
​
    // getters...
​
}
```

如果您要升级`Long`属性，请确保定义单位（使用`@DurationUnit`）（如果它不是毫秒）。这样做提供了透明的升级路径，同时支持更丰富的格式。

**转换期间**

除了持续时间之外，Spring Boot 还可以使用`java.time.Period`类型。应用程序属性中可以使用以下格式：

* 常规`int`表示（使用天作为默认单位，除非`@PeriodUnit`已指定 a）
* [使用的](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Period.html#parse\(java.lang.CharSequence\))标准 ISO-8601 格式[`java.time.Period`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Period.html#parse\(java.lang.CharSequence\))
* 一种更简单的格式，其中值和单位对耦合（`1y3d`表示 1 年零 3 天）

简单格式支持以下单位：

* `y`年
* `m`月
* `w`周
* `d`天

> 该`java.time.Period`类型实际上从不存储周数，它是一个快捷方式，意思是“7 天”。

**转换数据大小**

Spring框架有一个以`DataSize`字节为单位表示大小的值类型。如果您公开`DataSize`属性，则应用程序属性中的以下格式可用：

* 正则`long`表示（除非指定了`@DataSizeUnit`，否则使用字节作为默认单位）
* 一种更易读的格式，其中值和单位耦合（`10MB`表示 10 兆字节）

考虑以下示例：

```
@ConfigurationProperties("my")
public class MyProperties {
​
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize bufferSize = DataSize.ofMegabytes(2);
​
    private DataSize sizeThreshold = DataSize.ofBytes(512);
​
    // getters/setters...
​
}
```

指定缓冲区大小为10兆字节，`10`与`10MB`是等价的。256 字节的大小阈值可以指定为`256`或`256B`。

您还可以使用任何受支持的单位。这些都是：

* `B`字节
* `KB`千字节
* `MB`兆字节
* `GB`千兆字节
* `TB`兆兆字节

默认单位是字节，可以使用`@DataSizeUnit`上面示例中所示的方法进行覆盖。

如果您更喜欢使用构造函数绑定，则可以公开相同的属性，如以下示例所示：

```
@ConfigurationProperties("my")
public class MyProperties {
​
    // fields...
​
    public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
            @DefaultValue("512B") DataSize sizeThreshold) {
        this.bufferSize = bufferSize;
        this.sizeThreshold = sizeThreshold;
    }
​
    // getters...
​
}
```

> 如果要升级`Long`属性，请确保定义单位（使用`@DataSizeUnit`）（如果它不是字节）。这样做提供了透明的升级路径，同时支持更丰富的格式。

**@ConfigurationProperties 验证**

每当使用 Spring `@Validated`的注释对类进行注释时，Spring Boot 就会尝试验证`@ConfigurationProperties`类。您可以直接在配置类上使用 JSR-303约定的`jakarta.validation`注释。为此，请确保您的类路径上有兼容的 JSR-303 实现，然后向您的字段添加约束注释，如以下示例所示：

```
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
​
    @NotNull
    private InetAddress remoteAddress;
​
    // getters/setters...
​
}
```

您还可以通过使用`@Validated`注释来对创建配置属性的`@Bean`方法来触发验证。

为了确保即使没有找到任何属性，也始终会触发嵌套属性的验证，关联字段必须使用`@Valid` 进行注释。以下示例基于前面的`MyProperties`示例：

```
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
​
    @NotNull
    private InetAddress remoteAddress;
​
    @Valid
    private final Security security = new Security();
​
    // getters/setters...
​
    public static class Security {
​
        @NotEmpty
        private String username;
​
        // getters/setters...
​
    }
​
}
```

您还可以通过创建名为`Validator` 的 bean 定义来添加自定义 Spring `configurationPropertiesValidator`。`@Bean`方法应该被声明`static`。配置属性验证器是在应用程序生命周期的早期创建的，并且将该`@Bean`方法声明为静态可以创建 bean，而无需实例化`@Configuration`类。这样做可以避免早期实例化可能引起的任何问题。

> `spring-boot-actuator`模块包括一个公开所有`@ConfigurationProperties` bean 的端点。将您的 Web 浏览器指向`/actuator/configprops`或使用等效的 JMX 端点。有关详细信息，请参阅“[生产就绪功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.endpoints)”部分。

**@ConfigurationProperties 与 @Value**

`@Value`注解是核心容器功能，它不提供与类型安全配置属性相同的功能。下表总结了`@ConfigurationProperties`和`@Value`支持的功能：

| 特征                                                                                                                                                       | `@ConfigurationProperties` | `@Value`                                                                                                                                                                |
| -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [宽松绑定](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.relaxed-binding) | 是的                         | 有限（见[下面的注释](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.vs-value-annotation.note)） |
| [元数据支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.configuration-metadata)                                           | 是的                         | 不                                                                                                                                                                       |
| `SpEL`表达式                                                                                                                                                | 不                          | 是的                                                                                                                                                                      |

> 如果您确实想使用`@Value`，我们建议您使用规范形式（仅使用小写字母的短横线大小写）来引用属性名称。这将允许 Spring Boot 使用与宽松绑定 `@ConfigurationProperties` 时相同的逻辑。
>
> 例如，`@Value("${demo.item-price}")`将从`application.properties`文件中获取`demo.item-price`和`demo.itemPrice`表单，以及从系统环境中获取`DEMO_ITEMPRICE`。如果您使用 `@Value("${demo.itemPrice}")`代替，则不会考虑 `demo.item-price`和 `DEMO_ITEMPRICE`。
>
> 如果您为自己的组件定义一组配置键，我们建议您将它们分组在用 注释的 POJO 中`@ConfigurationProperties`。这样做将为您提供结构化的、类型安全的对象，您可以将其注入到您自己的 bean 中。

`SpEL`在解析这些文件并填充环境时，不会处理[应用程序属性文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.files)中的表达式 。但是，可以`SpEL`在 中编写表达式`@Value`。如果应用程序属性文件中的属性值是一个`SpEL`表达式，则在通过`@Value`.


# 7.3.Profile配置

Spring Profiles 提供了一种分离应用程序配置部分并使其仅在某些环境中可用的方法。任何`@Component`，`@Configuration`或`@ConfigurationProperties`注解都可以在加载时使用`@Profile`标记进行限制，如下例所示：

```java
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {

    // ...

}
```

如果bean是通过`@ConfigurationProperties`注册而不是通过`@EnableConfigurationProperties`自动扫描注册的，则需要在`@EnableConfigurationProperties`中有`@Configuration`注解的类上指定`@Profile`注解。在被`@ConfigurationProperties`扫描的情况下，`@Profile`可以在`@ConfigurationProperties`类本身上指定。 您可以使用`spring.profiles.active` `Environment`属性来指定哪些配置文件处于活动状态。您可以通过本章前面描述的任何方式指定属性。例如，您可以将它包含在您的`application.properties`中，如以下示例所示：

```properties
spring.profiles.active=dev,hsqldb
```

您还可以使用以下开关在命令行上指定它：`--spring.profiles.active=dev,hsqldb`.

如果没有配置文件处于活动状态，则启用默认配置文件。默认配置文件的名称是`default`，并且可以使用`Environment`的`spring.profiles.default`属性进行调整，如以下示例所示：

```properties
spring.profiles.default=none
```

`spring.profiles.active`和`spring.profiles.default`只能在非配置文件特定文档中使用。这意味着它们不能通过`spring.config.activate.on-profile`包含在[配置文件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.activation-properties)或[特定配置文件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific)中.

例如第二个文档配置无效：

```properties
# this document is valid
spring.profiles.active=prod
#---
# this document is invalid
spring.config.activate.on-profile=prod
spring.profiles.active=metrics
```

#### 7.3.1.添加活动配置文件

`spring.profiles.active`属性遵循与其他属性相同的排序规则：`PropertySource`最高者获胜。这意味着您可以在`application.properties`中指定活动配置文件，然后使用命令行开关**替换它们。**

有时，将属性**添加**到活动配置文件而不是替换它们很有用。`spring.profiles.include`属性可用于在由`spring.profiles.active`属性激活的配置文件之上添加活动配置文件。`SpringApplication`入口还有一个用于设置附加配置文件的 Java API 。请参阅[SpringApplication](https://docs.spring.io/spring-boot/docs/2.7.3/api/org/springframework/boot/SpringApplication.html)中的`setAdditionalProfiles()`方法。

例如，当运行具有以下属性的应用程序时，即使使用 `--spring.profiles.active` 开关运行，也会激活公共和本地配置文件：

```properties
spring.profiles.include[0]=common
spring.profiles.include[1]=local
```

与`spring.profiles.active`类似，`spring.profiles.include`只能在非配置文件特定文档中使用。这意味着它不能通过`spring.config.activate.on-profile`包含在[配置文件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.activation-properties)或[特定配置文件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific)中. 如果给定的配置文件处于活动状态，则在[下一节](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles.groups)中描述的配置文件组也可用于添加活动配置文件。

#### 7.3.2. 配置文件组

有时，您在应用程序中定义和使用的配置文件过于细化，使用起来很麻烦。例如，您可能拥有用于独立启用数据库和消息传递功能的`proddb`和`prodmq`配置文件。

为了帮助解决这个问题，Spring Boot 允许您定义配置文件组。配置文件组允许您为相关的配置文件组定义逻辑名称。

例如，我们可以创建一个由我们的`proddb`和`prodmq`配置文件组成的`production`组。

```properties
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
```

我们的应用程序现在可以启动，使用`--spring.profiles.active=production`参数激活`production`,`proddb`和`prodmq`配置文件。

#### 7.3.3. 以编程方式设置配置文件

您可以通过在应用程序运行之前调用`SpringApplication.setAdditionalProfiles(…)`来以编程方式设置活动配置文件。也可以使用 Spring 的`ConfigurableEnvironment`接口激活配置文件。

#### 7.3.4. 配置文件特定的配置文件

`application.properties`（或`application.yml`）和通过引用的文件的特定配置文件变体被视为文件并通过`@ConfigurationProperties`加载。有关详细信息，请参阅“[配置文件特定文件](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific)”。

###


# 7.4.日志记录

Spring Boot 使用[Commons Logging](https://commons.apache.org/logging)进行所有内部日志记录，但对底层日志实现打开。为[Java Util Logging](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html)、[Log4J2](https://logging.apache.org/log4j/2.x/)和[Logback](https://logback.qos.ch/)提供了默认配置。在每种情况下，记录器都预先配置为使用控制台输出，并且还提供可选的文件输出。

默认情况下，如果您使用“Starters”，则使用 Logback 进行日志记录。还包括适当的 Logback 路由，以确保使用 Java Util Logging、Commons Logging、Log4J 或 SLF4J 的依赖库都能正常工作。 有很多可用于 Java 的日志记录框架。如果上面的列表看起来令人困惑，请不要担心。通常，您不需要更改日志记录依赖项，Spring Boot 默认值就可以正常工作。 当您将应用程序部署到 servlet 容器或应用程序服务器时，使用 Java Util Logging API 执行的日志记录不会路由到应用程序的日志中。这可以防止容器或已部署到它的其他应用程序执行的日志记录出现在应用程序的日志中。

#### 7.4.1日志格式

Spring Boot 的默认日志输出类似于以下示例：

```
2023-06-22T12:08:05.861Z  INFO 22768 --- [           main] o.s.b.d.f.s.MyApplication                : Starting MyApplication using Java 17.0.7 with PID 22768 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-06-22T12:08:05.872Z  INFO 22768 --- [           main] o.s.b.d.f.s.MyApplication                : No active profile set, falling back to 1 default profile: "default"
2023-06-22T12:08:09.854Z  INFO 22768 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-06-22T12:08:09.892Z  INFO 22768 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-06-22T12:08:09.892Z  INFO 22768 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.10]
2023-06-22T12:08:10.160Z  INFO 22768 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-06-22T12:08:10.162Z  INFO 22768 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4038 ms
2023-06-22T12:08:11.512Z  INFO 22768 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-06-22T12:08:11.534Z  INFO 22768 --- [           main] o.s.b.d.f.s.MyApplication                : Started MyApplication in 7.251 seconds (process running for 8.584)
```

输出以下内容：

* 日期和时间：毫秒精度，易于排序。
* 日志级别：`ERROR`、`WARN`、`INFO`、`DEBUG`或`TRACE`。
* 进程标识。
* `---`用于区分实际日志消息开始的分隔符。
* 线程名称：括在方括号中（可能会被截断以用于控制台输出）。
* 记录器名称：这通常是源类名称（通常缩写）。
* 日志消息。

> Logback 没有`FATAL`级别。它被映射到`ERROR`上。

#### 7.4.2. 控制台输出

默认日志配置在写入时将消息回显到控制台。默认情况下，会记录`ERROR`、`WARN`和`INFO`级别消息。您还可以通过使用`--debug`标志启动应用程序来启用“调试”模式。

```shell
$ java -jar myapp.jar --debug
```

你也可以在你的`application.properties`配置`debug=true`.

启用调试模式后，会配置一系列核心记录器（嵌入式容器、Hibernate 和 Spring Boot）以输出更多信息。启用调试模式不会*将*您的应用程序配置为记录所有`DEBUG`级别的消息。

或者，您可以通过使用`--trace`标志或在您的`application.properties`配置`trace=true`. 这样做可以为选择的核心记录器（嵌入式容器、Hibernate 模式生成和整个 Spring 产品组合）启用跟踪日志记录。

7.**4.2.1. 颜色编码输出**

如果您的终端支持 ANSI，则可以使用颜色输出来提高可读性。您可以设置`spring.output.ansi.enabled`为[支持的值](https://docs.spring.io/spring-boot/docs/2.7.3/api/org/springframework/boot/ansi/AnsiOutput.Enabled.html)以覆盖自动检测配置。

使用`%clr`转换字配置颜色编码。在最简单的形式中，转换器根据日志级别为输出着色，如以下示例所示：

```
%clr(%5p)
```

下表描述了日志级别到颜色的映射：

| 等级      | 颜色  |
| ------- | --- |
| `FATAL` | 红色的 |
| `ERROR` | 红色的 |
| `WARN`  | 黄色  |
| `INFO`  | 绿色的 |
| `DEBUG` | 绿色的 |
| `TRACE` | 绿色的 |

或者，您可以通过将其作为转换选项提供来指定应使用的颜色或样式。例如，要将文本变为黄色，请使用以下设置：

```
%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}
```

支持以下颜色和样式：

* `blue`
* `cyan`
* `faint`
* `green`
* `magenta`
* `red`
* `yellow`

#### 7.4.3. 文件输出

默认情况下，Spring Boot 仅打印日志到控制台，不写入日志文件。如果您想在控制台输出之外写入日志文件，您需要设置一个`logging.file.name`或者`logging.file.path`属性（例如，在您的`application.properties`中）.

下表显示了如何`logging.*`一起使用这些属性：

| `logging.file.name` | `logging.file.path` | 例子         | 描述                                            |
| ------------------- | ------------------- | ---------- | --------------------------------------------- |
| *（无）*               | *（无）*               |            | 仅控制台日志记录。                                     |
| 具体文件                | *（无）*               | `my.log`   | 写入指定的日志文件。名称可以是确切的位置，也可以是相对于当前目录的位置。          |
| *（无）*               | 具体目录                | `/var/log` | 写入`spring.log`指定目录。名称可以是确切的位置，也可以是相对于当前目录的位置。 |

日志文件在达到 10 MB 时会轮换，并且与控制台输出一样，默认情况下会记录`ERROR`、`WARN`和`INFO`消息。

> 日志记录属性独立于实际的日志记录基础设施。因此，特定的配置键（例如 Logback的`logback.configurationFile`）不由 spring Boot 管理。

#### 7.4.4. 文件轮换

如果您使用的是 Logback，则可以使用您的`application.properties`或`application.yaml`文件微调日志轮换设置。对于所有其他日志记录系统，您需要自己直接配置轮换设置（例如，如果您使用 Log4J2，那么您可以添加一个`log4j2.xml`或`log4j2-spring.xml`文件）。

支持以下轮换策略属性：

| 姓名                                                     | 描述                      |
| ------------------------------------------------------ | ----------------------- |
| `logging.logback.rollingpolicy.file-name-pattern`      | 用于创建日志存档的文件名模式。         |
| `logging.logback.rollingpolicy.clean-history-on-start` | 如果应在应用程序启动时进行日志归档清理。    |
| `logging.logback.rollingpolicy.max-file-size`          | 归档前日志文件的最大大小。           |
| `logging.logback.rollingpolicy.total-size-cap`         | 在被删除之前可以占用的最大大小的日志档案。   |
| `logging.logback.rollingpolicy.max-history`            | 要保留的存档日志文件的最大数量（默认为 7）。 |

#### 7.4.5. 日志级别

所有受支持的日志记录系统都可以通过`logging.level.<logger-name>=<level>`使用 TRACE、DEBUG、INFO、WARN、ERROR、FATAL 或 OFF `level`之一在 Spring `Environment`中设置记录器级别（例如在 `application.properties`中）。 `root`记录器可以使用`logging.level.root`设置.

以下示例显示了潜在的日志记录设置`application.properties`：

```properties
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
```

也可以使用环境变量设置日志记录级别。例如，`LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG`将设置`org.springframework.web`为`DEBUG`。 上述方法仅适用于包级别的日志记录。由于宽松绑定总是将环境变量转换为小写，因此无法以这种方式为单个类配置日志记录。如果需要为类配置日志记录，可以使用[`SPRING_APPLICATION_JSON`](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.application-json)变量。

#### 7.4.6. 日志组

能够将相关的记录器组合在一起通常很有用，以便可以同时配置它们。例如，您可能通常会更改*所有*与 Tomcat 相关的记录器的日志记录级别，但您不容易记住顶级包。

为了解决这个问题，Spring Boot 允许您在 Spring `Environment`中定义日志记录组。例如，您可以通过以下方式定义“tomcat”组，方法是将其添加到您的`application.properties`:

```properties
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
```

定义后，您可以使用一行更改组中所有记录器的级别：

```properties
logging.level.tomcat=trace
```

Spring Boot 包括以下可开箱即用的预定义日志记录组：

| 名称  | 日志loggers                                                                                                                                                                                                      |
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| web | `org.springframework.core.codec`, `org.springframework.http`, `org.springframework.web`, `org.springframework.boot.actuate.endpoint.web`,`org.springframework.boot.web.servlet.ServletContextInitializerBeans` |
| sql | `org.springframework.jdbc.core`, `org.hibernate.SQL`,`org.jooq.tools.LoggerListener`                                                                                                                           |

#### 7.4.7. 使用日志关闭挂钩

为了在您的应用程序终止时释放日志资源，提供了一个关闭挂钩，该挂钩将在 JVM 退出时触发日志系统清理。除非您的应用程序部署为 war 文件，否则此关闭挂钩会自动注册。如果您的应用程序具有复杂的上下文层次结构，则关闭挂钩可能无法满足您的需求。如果没有，请禁用关闭挂钩并调查底层日志系统直接提供的选项。例如，Logback 提供[上下文选择器](https://logback.qos.ch/manual/loggingSeparation.html)，允许每个 Logger 在其自己的上下文中创建。您可以使用`logging.register-shutdown-hook`属性来禁用关闭挂钩。将其设置为`false`将禁用注册。您可以在`application.properties`或者`application.yaml`文件中设置属性：

```properties
logging.register-shutdown-hook=false
```

#### 7.4.8. 自定义日志配置

各种日志系统可以通过在类路径中包含适当的库来激活，并且可以通过在类路径的根目录或由以下 Spring`Environment`属性指定的位置提供合适的配置文件来进一步定制：`logging.config`

您可以使用system 属性`org.springframework.boot.logging.LoggingSystem`强制 Spring Boot 使用特定的日志记录系统。该值应该是实现的完全限定类名`LoggingSystem`。您还可以使用`none`.

由于在创建`ApplicationContext`**之前** 初始化了日志记录，因此无法从Spring`@Configuration`文件中使用`@PropertySources`控制日志记录。更改日志系统或完全禁用它的唯一方法是通过系统属性。 根据您的日志记录系统，将加载以下文件：

| 对应日志框架             | 定制                                                                              |
| ------------------ | ------------------------------------------------------------------------------- |
| logback            | `logback-spring.xml`, `logback-spring.groovy`, `logback.xml`, 或`logback.groovy` |
| log4j2             | `log4j2-spring.xml`或者`log4j2.xml`                                               |
| JDK（Java 实用程序日志记录） | `logging.properties`                                                            |

> 如果可能，我们建议您为日志配置使用`-spring`变体（例如，`logback-spring.xml`而不是`logback.xml`）。如果使用标准配置位置，Spring 无法完全控制日志初始化。
>
> Java Util Logging 存在已知的类加载问题，这些问题在从“可执行 jar”运行时会导致问题。如果可能，我们建议您在从“可执行 jar”运行时避免使用它。

为了帮助进行自定义，一些其他属性从 Spring 转移`Environment`到 System 属性，如下表所述：

| spring属性                            | 系统属性                            | 注释                              |
| ----------------------------------- | ------------------------------- | ------------------------------- |
| `logging.exception-conversion-word` | `LOG_EXCEPTION_CONVERSION_WORD` | 记录异常时使用的转换字。                    |
| `logging.file.name`                 | `LOG_FILE`                      | 如果已定义，它将在默认日志配置中使用。             |
| `logging.file.path`                 | `LOG_PATH`                      | 如果已定义，它将在默认日志配置中使用。             |
| `logging.pattern.console`           | `CONSOLE_LOG_PATTERN`           | 在控制台上使用的日志模式 (stdout)。          |
| `logging.pattern.dateformat`        | `LOG_DATEFORMAT_PATTERN`        | 日志日期格式的附加模式。                    |
| `logging.charset.console`           | `CONSOLE_LOG_CHARSET`           | 用于控制台日志记录的字符集。                  |
| `logging.pattern.file`              | `FILE_LOG_PATTERN`              | 在文件中使用的日志模式（如果`LOG_FILE`启用）。    |
| `logging.charset.file`              | `FILE_LOG_CHARSET`              | 用于文件日志记录的字符集（如果`LOG_FILE`启用）。   |
| `logging.pattern.level`             | `LOG_LEVEL_PATTERN`             | 呈现日志级别时使用的格式（默认`%5p`）。          |
| `PID`                               | `PID`                           | 当前进程 ID（如果可能且尚未定义为 OS 环境变量时发现）。 |

如果使用 Logback，还会传输以下属性：

| spring属性                                               | 系统属性                                           | 注释                                                |
| ------------------------------------------------------ | ---------------------------------------------- | ------------------------------------------------- |
| `logging.logback.rollingpolicy.file-name-pattern`      | `LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN`      | 翻转日志文件名的模式（默认`${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz`）。 |
| `logging.logback.rollingpolicy.clean-history-on-start` | `LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START` | 是否在启动时清理归档日志文件。                                   |
| `logging.logback.rollingpolicy.max-file-size`          | `LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE`          | 最大日志文件大小。                                         |
| `logging.logback.rollingpolicy.total-size-cap`         | `LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP`         | 要保留的日志备份的总大小。                                     |
| `logging.logback.rollingpolicy.max-history`            | `LOGBACK_ROLLINGPOLICY_MAX_HISTORY`            | 要保留的存档日志文件的最大数量。                                  |

所有受支持的日志记录系统在解析其配置文件时都可以查阅系统属性。`spring-boot.jar`有关示例，请参见中的默认配置：

* [logback](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml)
* [log4j2](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml)
* [Java 实用程序日志记录](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/java/logging-file.properties)

> 如果你想在日志属性中使用占位符，你应该使用[Spring Boot 的语法](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.property-placeholders)，而不是底层框架的语法。值得注意的是，如果你使用 Logback，你应该使用`:`作为属性名称和它的默认值之间的分隔符，而不是使用`:-`.
>
> 您可以通过仅覆盖`LOG_LEVEL_PATTERN`（或使用 Logback的`logging.pattern.level`）的方式将 MDC 和其他临时内容添加到日志行。例如，配置为`logging.pattern.level=user:%X{user} %5p`，则默认日志格式包含“用户”的 MDC 条目（如果存在），如以下示例所示。
>
> `2019-08-30 12:30:04.031 user:someone INFO 22174 --- [ nio-8080-exec-0] demo.Controller Handling authenticated request`

#### 7.4.9. Logback 扩展

Spring Boot 包含许多对 Logback 的扩展，可以帮助进行高级配置。您可以在`logback-spring.xml`配置文件中使用这些扩展。

因为标准`logback.xml`配置文件加载得太早，所以不能在里面使用扩展。您需要使用`logback-spring.xml`或定义`logging.config`属性。 扩展不能与 Logback 的[配置扫描](https://logback.qos.ch/manual/configuration.html#autoScan)一起使用。如果您尝试这样做，则对配置文件进行更改会导致记录类似于以下内容之一的错误：

```
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]]
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]
```

7.**4.9.1. 配置文件特定的配置**

`<springProfile>`标签允许您根据活动的 Spring 配置文件选择性地包含或排除配置部分。`<configuration>`元素内的任何位置都支持配置文件部分。使用`name`属性来指定哪个配置文件接受配置。`<springProfile>`标签可以包含配置文件名称（例如`staging`）或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑，例如`production & (eu-central | eu-west)`. 查看[参考指南](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/core.html#beans-definition-profiles-java)以获取更多详细信息。以下清单显示了三个示例配置文件：

```xml
<springProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
```

7.**4.9.2. 环境属性**

`<springProperty>`标签允许您公开 Spring `Environment`中的属性以在 Logback 中使用。如果您想在 Logback 配置中访问`application.properties`文件中的值，这样做会很有用。该标签的工作方式与 Logback 的标准`<property>`标签类似。但是，您可以指定属性的 `source`（来自`Environment`） ，而不是直接指定`value`。如果您需要将属性存储在`local`范围以外的某个位置，则可以使用`scope`属性。如果您需要一个备用值（如果该属性未在`Environment` 中设置），您可以使用`defaultValue`属性。以下示例显示了如何公开属性以在 Logback 中使用：

```xml
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
        defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
    <remoteHost>${fluentHost}</remoteHost>
    ...
</appender>
```

必须在 kebab case中指定`source`（例如`my.property-name`）。但是，可以使用宽松的规则将属性添加到`Environment` 中。

**7.4.10. Log4j2 扩展**

Spring Boot 包含许多对 Log4j2 的扩展，可以帮助进行高级配置。您可以在任何`log4j2-spring.xml`配置文件中使用这些扩展。

> 由于标准的`log4j2.xml`配置文件加载得太早，因此无法在其中使用扩展。您需要使用`log4j2-spring.xml`或定义一个`logging.config`属性。
>
> 这些扩展取代了Log4J 提供的[Spring Boot 支持。](https://logging.apache.org/log4j/2.x/log4j-spring-boot/index.html)您应该确保不要将`org.apache.logging.log4j:log4j-spring-boot`模块包含在您的构建中。

**特定于配置文件的配置**

`<SpringProfile>`标签允许您根据活动的 Spring 配置文件选择包含或排除配置部分。`<Configuration>`元素内的任何位置都支持配置文件部分。使用`name`属性指定哪个配置文件接受配置。`<SpringProfile>`标签可以包含配置文件名称（例如`staging`）或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑，例如`production & (eu-central | eu-west)`。查看[Spring 框架参考指南](https://docs.spring.io/spring-framework/reference/6.1/core/beans/environment.html#beans-definition-profiles-java)以获取更多详细信息。以下清单显示了三个示例配置文件：

```
<SpringProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</SpringProfile>
​
<SpringProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</SpringProfile>
​
<SpringProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</SpringProfile>
```

**环境属性查找**

如果您想在 Log4j2 配置中引用 Spring `Environment`的属性，您可以使用带`spring:`前缀的[查找](https://logging.apache.org/log4j/2.x/manual/lookups.html)。如果您想要访问Log4j2 配置中的`application.properties`文件中的值，那么这样做会很有用。

以下示例显示如何设置`applicationName`的Log4j2 属性，从 Spring `Environment`中读取`spring.application.name`：

```
<Properties>
    <Property name="applicationName">${spring:spring.application.name}</Property>
</Properties>
```

> 查找键应以短横线大小写指定（例如`my.property-name`）。

**Log4j2 系统属性**

Log4j2 支持许多可用于配置各种项目的[系统属性。](https://logging.apache.org/log4j/2.x/manual/configuration.html#SystemProperties)例如，系统属性`log4j2.skipJansi`可用于配置`ConsoleAppender`是否尝试在 Windows 上使用[Jansi输出流。](https://github.com/fusesource/jansi)

Log4j2 初始化后加载的所有系统属性都可以从 Spring`Environment`获取。例如，您可以添加`log4j2.skipJansi=false`到`application.properties`文件中以便`ConsoleAppender`在 Windows 上使用 Jansi。

> 仅当系统属性和操作系统环境变量不包含正在加载的值时才考虑 Spring `Environment`。
>
> 在早期 Log4j2 初始化期间加载的系统属性无法引用 Spring `Environment`。例如，在 Spring 环境可用之前，使用 Log4j2 属性来允许选择默认的 Log4j2 实现。


# 7.5.国际化

Spring Boot 支持本地化消息，以便您的应用程序可以满足不同语言偏好的用户。默认情况下，Spring Boot在类路径的根目录中查找`messages`资源包的存在。

当配置的资源包的默认属性文件可用时（默认情况下为`messages.properties`），自动配置适用。如果您的资源包仅包含特定于语言的属性文件，则需要添加默认值。如果没有找到与任何配置的基本名称匹配的属性文件，则不会有自动配置的 `MessageSource`。 可以使用`spring.messages`命名空间配置资源包的基本名称以及其他几个属性，如下例所示：

```
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
```

`spring.messages.basename`支持以逗号分隔的位置列表，可以是包限定符，也可以是从类路径根解析的资源。 更多支持选项，请参阅[`MessageSourceProperties`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.java)。


# 7.6 面向切面的编程

Spring Boot 为面向切面编程（AOP）提供自动配置。[您可以在Spring 框架参考文档](https://docs.spring.io/spring-framework/reference/6.1/core/aop-api.html)中了解有关 Spring 的 AOP 的更多信息。

默认情况下，Spring Boot 的自动配置将 Spring AOP 配置为使用 CGLib 代理。要改为使用 JDK 代理，请设置`configprop:spring.aop.proxy-target-class`为`false`.

如果 AspectJ 在类路径上，Spring Boot 的自动配置将自动启用 AspectJ 自动代理，这样就不需要`@EnableAspectJAutoProxy`了。

<br>


# 7.7. JSON

Spring Boot 提供与三个 JSON 映射库的集成：

* gson
* Jackson
* JSON-B

Jackson 是首选的默认库。

#### 7.7.1. Jackson

提供了 Jackson 的自动配置，并且 Jackson 是`spring-boot-starter-json`. 当 Jackson 在类路径上时，`ObjectMapper`会自动配置一个 bean。提供了几个配置属性用于[自定义`ObjectMapper`](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.spring-mvc.customize-jackson-objectmapper).

**6.1.1. 自定义序列化器和反序列化器**

如果您使用 Jackson 来序列化和反序列化 JSON 数据，您可能需要编写自己的`JsonSerializer`类和`JsonDeserializer`类。自定义序列化程序通常[通过模块向 Jackson 注册](https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers)，但 Spring Boot 提供了一个`@JsonComponent`注解，可以更轻松地直接注册 Spring Bean。

您可以直接在`JsonSerializer` `JsonDeserializer` 或`KeyDeserializer`实现上使用`@JsonComponent`注释。您还可以在包含序列化器/反序列化器作为内部类的类上使用它，如以下示例所示：

```
@JsonComponent
public class MyJsonComponent {
​
    public static class Serializer extends JsonSerializer<MyObject> {
​
        @Override
        public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            jgen.writeStartObject();
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
            jgen.writeEndObject();
        }
​
    }
​
    public static class Deserializer extends JsonDeserializer<MyObject> {
​
        @Override
        public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
            ObjectCodec codec = jsonParser.getCodec();
            JsonNode tree = codec.readTree(jsonParser);
            String name = tree.get("name").textValue();
            int age = tree.get("age").intValue();
            return new MyObject(name, age);
        }
​
    }
​
}
```

`ApplicationContext`中的所有`@JsonComponent`bean 都会自动向 Jackson 注册。因为`@JsonComponent`是用元注解的`@Component`，所以通常的组件扫描规则适用。

Spring Boot 还提供了[`JsonObjectSerializer`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonObjectSerializer.java)和[`JsonObjectDeserializer`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonObjectDeserializer.java)基类，它们在序列化对象时提供了标准 Jackson 版本的有用替代方案。有关详细信息，请参阅[`JsonObjectSerializer`](https://docs.spring.io/spring-boot/docs/2.7.3/api/org/springframework/boot/jackson/JsonObjectSerializer.html)和[`JsonObjectDeserializer`](https://docs.spring.io/spring-boot/docs/2.7.3/api/org/springframework/boot/jackson/JsonObjectDeserializer.html)的Javadoc。

上面的例子可以重写为使用`JsonObjectSerializer`/`JsonObjectDeserializer`如下：

```
@JsonComponent
public class MyJsonComponent {
​
    public static class Serializer extends JsonObjectSerializer<MyObject> {
​
        @Override
        protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException {
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
        }
​
    }
​
    public static class Deserializer extends JsonObjectDeserializer<MyObject> {
​
        @Override
        protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
                JsonNode tree) throws IOException {
            String name = nullSafeValue(tree.get("name"), String.class);
            int age = nullSafeValue(tree.get("age"), Integer.class);
            return new MyObject(name, age);
        }
​
    }
​
}
```

7.7.**1.2. 混合**

Jackson 支持 mixins，可用于将其他注解混合到已在目标类上声明的注释中。Spring Boot 的 Jackson 自动配置将扫描您应用程序的包以查找带有`@JsonMixin`注解的类，并将它们注册到自动配置的`ObjectMapper`中。通过 Spring Boot 的`JsonMixinModule`注册.

#### 7.7.2. gson

提供了 Gson 的自动配置。当 Gson 在类路径上时，会自动配置一个`Gson` bean。提供了几个`spring.gson.*`配置属性用于自定义配置。要进行更多控制，可以使用一个或多个`GsonBuilderCustomizer` bean。

#### 7.7.3. JSON-B

提供了 JSON-B 的自动配置。当 JSON-B API 和实现在类路径上时，将自动配置一个`Jsonb` bean。首选的 JSON-B 实现是为其提供依赖管理的 Apache Johnzon。


# 7.8. 任务执行与调度

###

在上下文中没有`Executor`bean 的情况下，Spring Boot 自动配置一个具有合理的默认值的`ThreadPoolTaskExecutor`，这些默认值可以自动关联到异步任务执行 ( `@EnableAsync`) 和 Spring MVC 异步请求处理。

如果您在上下文中定义了自定义`Executor`，则常规任务执行（即`@EnableAsync`）将透明地使用它，但配置不会支持 Spring MVC ，因为它需要对`AsyncTaskExecutor`进行实现（名为`applicationTaskExecutor`）。根据您的目标安排，您可以将您的`Executor`更改为 `ThreadPoolTaskExecutor`或定义 `ThreadPoolTaskExecutor`和`AsyncConfigurer`包装自定义的`Executor`。自动配置`TaskExecutorBuilder`允许您轻松创建实例来重现自动配置默认执行的操作。

线程池使用8个核心线程，可以根据负载增长和收缩。这些默认设置可以使用命名空间进行微调`spring.task.execution`，如以下示例所示：

```
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
```

这将线程池更改为使用有界队列，以便当队列已满（100 个任务）时，线程池增加到最多 16 个线程。由于线程在空闲 10 秒（而不是默认情况下为 60 秒）时被回收，因此池的收缩更具侵略性。

如果需要与计划的任务执行相关联（例如使用`@EnableScheduling`），也可以自动配置`ThreadPoolTaskScheduler`。线程池默认使用一个线程，可以使用命名空间`spring.task.scheduling`微调其设置，如下例所示：

```
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
```

如果需要创建自定义执行程序或调度程序，则`TaskExecutorBuilder`bean 和`TaskSchedulerBuilder`bean 在上下文中都可用。


# 7.9. 单元测试

Spring Boot 提供了许多实用程序和注解来帮助测试您的应用程序。测试支持由两个模块提供：包含核心项目的`spring-boot-test`，并支持测试自动配置的`spring-boot-test-autoconfigure`。

大多数开发人员使用`spring-boot-starter-test`“Starter”，它导入了 Spring Boot 测试模块以及 JUnit Jupiter、AssertJ、Hamcrest 和许多其他有用的库。

如果您有使用 JUnit 4 的测试，则可以使用 JUnit 5 的老式引擎来运行它们。要使用老式引擎，请添加对`junit-vintage-engine`的依赖项，如以下示例所示：

```xml
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
```

`org.hamcrest:hamcrest` 的`hamcrest-core`被排除在外，那是`spring-boot-starter-test`的一部分。

####

####

####

####


# 7.9.1. 测试范围依赖

“ `spring-boot-starter-test`Starter”（在`test` `scope`中）包含以下提供的库：

* [JUnit 5](https://junit.org/junit5/)：单元测试 Java 应用程序的事实标准。
* [Spring Test](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#integration-testing) & Spring Boot Test：Spring Boot 应用程序的实用程序和集成测试支持。
* [AssertJ](https://assertj.github.io/doc/)：一个流畅的断言库。
* [Hamcrest](https://github.com/hamcrest/JavaHamcrest)：匹配器对象库（也称为约束或谓词）。
* [Mockito](https://site.mockito.org/)：Java 模拟框架。
* [JSONassert](https://github.com/skyscreamer/JSONassert) : JSON 的断言库。
* [JsonPath](https://github.com/jayway/JsonPath)：JSON 的 XPath。

我们通常发现这些通用库在编写测试时很有用。如果这些库不适合您的需求，您可以添加自己的其他测试依赖项。


# 7.9.2. 测试 Spring 应用程序

依赖注入的主要优点之一是它应该使您的代码更容易进行单元测试。您可以使用`new`运算符来实例化对象，甚至无需涉及 Spring。您还可以使用*模拟对象*而不是真正的依赖项。

通常，您需要超越单元测试并开始集成测试（使用 Spring `ApplicationContext`）。能够执行集成测试而不需要部署您的应用程序或需要连接到其他基础设施是很有用的。

Spring 框架包括一个用于此类集成测试的专用测试模块。你可以直接声明一个`org.springframework:spring-test`依赖或者使用`spring-boot-starter-test`“Starter”来传递它。

如果您以前没有使用过该`spring-test`模块，则应首先阅读 Spring Framework 参考文档的[相关部分](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#testing)。


# 7.9.3. 测试 Spring Boot 应用程序

Spring Boot 应用程序是 Spring `ApplicationContext`，因此除了您通常使用普通 Spring 上下文执行的操作之外，无需进行任何特别的测试即可对其进行测试。

> 默认情况下，Spring Boot 的外部属性、日志记录和其他功能仅在您用于`SpringApplication`创建上下文时安装在上下文中。

Spring Boot 提供了`@SpringBootTest`注解，当您需要 Spring Boot 特性时，可以将其用作标准`spring-test` `@ContextConfiguration`注解的替代方案。该注解的工作原理是通过 SpringApplication 创建测试中使用的 ApplicationContext. 除了`@SpringBootTest`许多其他注解之外，还提供了用于[测试应用程序更具体的切面](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-tests)。

> 如果您使用的是 JUnit 4，请不要忘记也添加`@RunWith(SpringRunner.class)`到您的测试中，否则注解将被忽略。如果您使用的是 JUnit 5，不需要添加等效的 `@ExtendWith(SpringExtension.class)`作为`@SpringBootTest`，并且其他`@…Test`注解已经用它注解了。

默认情况下，`@SpringBootTest`不会启动服务器。您可以使用`@SpringBootTest` 的`webEnvironment`属性来进一步优化测试的运行方式：

* `MOCK`（默认）：加载web `ApplicationContext`并提供模拟网络环境。使用此注解时不启动嵌入式服务器。如果您的类路径上没有可用的 Web 环境，则此模式会透明地回退到创建常规的 non-web `ApplicationContext`。它可以与您的 Web 应用程序结合使用[`@AutoConfigureMockMvc`或`@AutoConfigureWebTestClient`](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-mock-environment)用于基于模拟的测试。
* `RANDOM_PORT`: 加载`WebServerApplicationContext`并提供一个真实的网络环境。嵌入式服务器会启动并侦听随机端口。
* `DEFINED_PORT`: 加载`WebServerApplicationContext`并提供一个真实的网络环境。嵌入式服务器会启动并侦听定义的端口（来自您的`application.properties`）或默认的`8080`端口.
* `NONE`：通过使用`SpringApplication`加载一个`ApplicationContext`但不提供*任何*Web 环境（模拟或其他）。

> 如果您的测试是`@Transactional`，默认情况下，它会在每个测试方法结束时回滚事务。然而，由于使用`RANDOM_PORT`或`DEFINED_PORT`隐含地提供了一个真正的 servlet 环境，HTTP 客户端和服务器在不同的线程中运行，因此在不同的事务中运行。在这种情况下，服务器上启动的任何事务都不会回滚。
>
> 如果您的应用程序为管理服务器使用不同的端口， 使用`webEnvironment = WebEnvironment.RANDOM_PORT`的 `@SpringBootTest`还将在单独的随机端口上启动管理服务器。

7.&#x39;**.3.1. 检测 Web 应用程序类型**

如果 Spring MVC 可用，则配置常规的基于 MVC 的应用程序上下文。如果您只有 Spring WebFlux，我们将检测到它并配置一个基于 WebFlux 的应用程序上下文。

如果两者都存在，则 Spring MVC 优先。如果要在这种情况下测试响应式 Web 应用程序，则必须设置`spring.main.web-application-type`属性：

```java
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {

    // ...

}
```

7.&#x39;**.3.2. 检测测试配置**

如果您熟悉 Spring Test Framework，您可能习惯于使用`@ContextConfiguration(classes=…)`以指定要加载的 Spring `@Configuration`。或者，您可能经常在测试中使用嵌套`@Configuration`类。

在测试 Spring Boot 应用程序时，通常不需要这样做。只要您没有明确定义，Spring Boot 的`@*Test`注解就会自动搜索您的主要配置。

搜索算法从包含测试的包开始，直到找到一个用`@SpringBootApplication`或`@SpringBootConfiguration`注解的类。只要您以合理的方式[构建代码](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.structuring-your-code)，通常会找到您的主要配置。

> 如果您使用[测试注解来测试应用程序的更具体部分](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-tests)，则应避免在[主方法的应用程序类](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.user-configuration-and-slicing)上添加特定于特定区域的配置设置。`@SpringBootApplication`的底层组件扫描配置定义了用于确保切面按预期工作的排除过滤器。如果您在 `@SpringBootApplication`注解的类上使用显式`@ComponentScan`指令，请注意这些过滤器将被禁用。如果您使用切面，则应重新定义它们。

如果要自定义主要配置，可以使用嵌套`@TestConfiguration`类。与将使用嵌套`@Configuration`类代替应用程序的主要配置不同，嵌套`@TestConfiguration`类是在应用程序的主要配置之外使用的。

> Spring 的测试框架在测试之间缓存应用程序上下文。因此，只要您的测试共享相同的配置（无论它是如何被发现的），加载上下文的潜在耗时过程只会发生一次。

7.&#x39;**.3.3. 排除测试配置**

如果您的应用程序使用组件扫描（例如，如果您使用`@SpringBootApplication`或`@ComponentScan`），您可能会发现仅为特定测试创建的顶级配置类意外地在各处被拾取。

正如我们[之前看到的](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.detecting-configuration)，`@TestConfiguration`可以在测试的内部类上使用来自定义主要配置。当放置在顶级类上时，`@TestConfiguration`表示不应通过扫描获取`src/test/java`中的类。然后，您可以在需要的地方显式导入该类，如以下示例所示：

```java
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {

    @Test
    void exampleTest() {
        // ...
    }

}
```

如果您直接使用`@ComponentScan`（即不通过`@SpringBootApplication`），则需要向`TypeExcludeFilter`注册。有关详细信息，请参阅[Javadoc](https://docs.spring.io/spring-boot/docs/2.7.3/api/org/springframework/boot/context/TypeExcludeFilter.html)。

7.&#x39;**.3.4. 使用应用程序参数**

如果您的应用程序需要[arguments](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.application-arguments)，您可以使用`@SpringBootTest`的`args`属性注入它们。

```java
@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {

    @Test
    void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
        assertThat(args.getOptionNames()).containsOnly("app.test");
        assertThat(args.getOptionValues("app.test")).containsOnly("one");
    }

}
```

7.&#x39;**.3.5. 使用模拟环境进行测试**

默认情况下，`@SpringBootTest`不会启动服务器，而是设置一个模拟环境来测试 Web 端点。

使用 Spring MVC，我们可以使用[`MockMvc`](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#spring-mvc-test-framework)或者`WebTestClient`查询我们的 Web 端点，如下例所示：

```java
@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {

    @Test
    void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
        mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
    }

    // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
    @Test
    void testWithWebTestClient(@Autowired WebTestClient webClient) {
        webClient
                .get().uri("/")
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("Hello World");
    }

}
```

> 如果您只想专注于 web 层而不是开始一个完整的`ApplicationContext`，请考虑[改用`@WebMvcTest`](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.spring-mvc-tests).

使用 Spring WebFlux 端点，您可以如下示例所示使用[`WebTestClient`](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#webtestclient-tests)：

```java
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }

}
```

在模拟环境中进行测试通常比使用完整的 servlet 容器运行更快。但是，由于 mocking 发生在 Spring MVC 层，依赖于较低级别 servlet 容器行为的代码不能直接使用 MockMvc 进行测试。例如，Spring Boot 的错误处理是基于 servlet 容器提供的“错误页面”支持。这意味着，虽然您可以按预期测试您的 MVC 层抛出和处理异常，但您不能直接测试是否呈现了特定的[自定义错误页面。](https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.servlet.spring-mvc.error-handling.error-pages)如果您需要测试这些较低级别的问题，您可以启动一个完全运行的服务器，如下一节所述。

7.&#x39;**.3.6. 使用正在运行的服务器进行测试**

如果您需要启动一个完整运行的服务器，我们建议您使用随机端口。如果您使用`@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)`，则每次运行测试时都会随机选择一个可用端口。

`@LocalServerPort`注解可用于将实际使用的[端口注入](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.discover-port)到您的测试中。为方便起见，需要对启动的服务器进行 REST 调用的测试可以通过额外添加`@Autowire`装配的[`WebTestClient`](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#webtestclient-tests)，它解析到正在运行的服务器的相关链接，并带有用于验证响应的专用 API，如下例所示：

```java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }

}
```

> `WebTestClient`可用于实时服务器和[模拟环境](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-mock-environment)。

此设置需要在类路径上有`spring-webflux`。如果你不能或不会添加 webflux，Spring Boot 还提供了一个`TestRestTemplate`工具：

```java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {

    @Test
    void exampleTest(@Autowired TestRestTemplate restTemplate) {
        String body = restTemplate.getForObject("/", String.class);
        assertThat(body).isEqualTo("Hello World");
    }

}
```

7.&#x39;**.3.7. 自定义 WebTestClient**

要自定义`WebTestClient`bean，请配置一个`WebTestClientBuilderCustomizer`bean。任何此类 bean 都使用`WebTestClient.Builder`创建`WebTestClient`.

7.&#x39;**.3.8. 使用 JMX**

由于测试上下文框架缓存了上下文，JMX 默认是禁用的，以防止相同的组件在同一个域上注册。如果此类测试需要访问`MBeanServer`，请考虑将其标记为dirty：

```java
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {

    @Autowired
    private MBeanServer mBeanServer;

    @Test
    void exampleTest() {
        assertThat(this.mBeanServer.getDomains()).contains("java.lang");
        // ...
    }

}
```

7.&#x39;**.3.9. 使用指标**

无论您的类路径如何，使用 `@SpringBootTest` 时，仪表注册表（内存中支持的除外）都不会自动配置。

如果您需要在集成测试中将指标导出到不同的后端，请使用`@AutoConfigureMetrics`.

7.&#x39;**.3.10. 模拟和窥探 Bean**

运行测试时，有时需要在应用程序上下文中模拟某些组件。例如，您可能有一些在开发期间不可用的远程服务的外观。当您想要模拟在真实环境中可能难以触发的故障时，模拟也很有用。

Spring Boot 包含一个`@MockBean`注解，可用于为您的`ApplicationContext`. 您可以使用注解添加新 bean 或替换单个现有 bean 定义。注解可以直接用于测试类、测试中的字段或`@Configuration`类和字段。在字段上使用时，创建的模拟实例也会被注入。在每个测试方法之后，模拟 bean 都会自动重置。

> 如果您的测试使用 Spring Boot 的测试注解之一（例如`@SpringBootTest`），则会自动启用此功能。要以不同的排列方式使用此功能，必须显式添加侦听器，如以下示例所示：
>
> ```
> @ContextConfiguration(classes = MyConfig.class)
> @TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })
> class MyTests {
>
>  // ...
>
> }
> ```

以下示例将现有`RemoteService`bean 替换为模拟实现：

```java
@SpringBootTest
class MyTests {

    @Autowired
    private Reverser reverser;

    @MockBean
    private RemoteService remoteService;

    @Test
    void exampleTest() {
        given(this.remoteService.getValue()).willReturn("spring");
        String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService
        assertThat(reverse).isEqualTo("gnirps");
    }

}
```

> `@MockBean`不能用于模拟在应用程序上下文刷新期间执行的 bean 的行为。到执行测试时，应用程序上下文刷新已完成，配置模拟行为为时已晚。我们建议在这种情况下使用`@Bean`方法来创建和配置模拟。

此外，您可以使用 `@SpyBean` 将任何现有的 `bean` 与 `Mockito spy` 包装在一起。有关完整详细信息，请参阅 Javadoc。

> CGLib 代理，例如为作用域 bean 创建的代理，将代理方法声明为`final`. 这会阻止 Mockito 正常运行，因为它无法在其默认配置中模拟或监视`final`方法。如果您想模拟或监视这样的 bean，请将 Mockito 配置为通过`org.mockito:mockito-inline`添加到应用程序的测试依赖项来使用其内联模拟生成器。这允许 Mockito 模拟和监视`final`方法。 虽然 Spring 的测试框架在测试之间缓存应用程序上下文，并为共享相同配置的测试重用上下文，但使用`@MockBean`或`@SpyBean`影响缓存键，这很可能会增加上下文的数量。 如果您使用`@SpyBean`通过名称引用参数的`@Cacheable`方法来监视 bean ，则您的应用程序必须使用`-parameters`. 这确保了一旦 bean 被监视，参数名称对缓存基础设施可用。 当您使用`@SpyBean`用于监视由 Spring 代理的 bean 时，您可能需要在某些情况下删除 Spring 的代理，例如在使用`given`或`when`设置期望值时。使用`AopTestUtils.getTargetObject(yourProxiedSpy)`也需要这样做。

7.&#x39;**.3.11. 自动配置的测试**

Spring Boot 的自动配置系统适用于应用程序，但有时对于测试来说有点太多了。它通常有助于仅加载测试应用程序“切面”所需的配置部分。例如，您可能想要测试 Spring MVC 控制器是否正确映射 URL，并且您不想在这些测试中涉及数据库调用，或者您可能想要测试 JPA 实体，而当这些测试运行。

`spring-boot-test-autoconfigure`模块包含许多注解，可用于自动配置此类“切面”。它们中的每一个都以类似的方式工作，它们中的每一个都以类似的方式工作，提供一个加载`ApplicationContext`的 `@…Test` 注解和一个或多个可用于自定义自动配置设置的`@AutoConfigure…`注解。

> 每个切面将组件扫描限制到适当的组件并加载一组非常有限的自动配置类。如果您需要排除其中一个，大多数`@…Test`注解都提供了一个`excludeAutoConfiguration`属性。或者，您也可以使用`@ImportAutoConfiguration#exclude`.
>
> 不支持通过在一个测试中使用多个`@…Test`注解来包含多个“切面” 。如果您需要多个“切面”，请选择其中一个`@…Test`注解并手动包含其他“切面”的`@AutoConfigure…`注解。
>
> 如果您对“切面”您的应用程序不感兴趣，但您想要一些自动配置的测试 bean，您也可以将`@AutoConfigure…`注解与标准`@SpringBootTest`注解一起使用。

7.&#x39;**.3.12. 自动配置的 JSON 测试**

要测试对象 JSON 序列化和反序列化是否按预期工作，您可以使用`@JsonTest`注解。 `@JsonTest`自动配置可用的支持 JSON 映射器，它可以是以下库之一：

* Jackson`ObjectMapper`，任何`@JsonComponent`bean和任何Jackson `Module`的
* `Gson`
* `Jsonb`

> 可以[在附录](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)中找到启用`@JsonTest`的自动配置列表。

如果需要配置自动配置的元素，可以使用`@AutoConfigureJsonTesters`注解。

Spring Boot 包括基于 AssertJ 的帮助程序，它们与 JSONAssert 和 JsonPath 库一起检查 JSON 是否按预期显示。`JacksonTester`、`GsonTester`和`BasicJsonTester`类可分别用于 Jackson、Gson、Jsonb 和 Strings 。使用 `@JsonTest` 时，测试类上的任何辅助字段都可以是 `@Autowired`。. 以下示例显示了 Jackson 的测试类：

```java
@JsonTest
class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

    @Test
    void serialize() throws Exception {
        VehicleDetails details = new VehicleDetails("Honda", "Civic");
        // Assert against a `.json` file in the same package as the test
        assertThat(this.json.write(details)).isEqualToJson("expected.json");
        // Or use JSON path based assertions
        assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
        assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
    }

    @Test
    void deserialize() throws Exception {
        String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
        assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
        assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
    }

}
```

JSON 辅助类也可以直接用于标准单元测试。为此，如果您不使用 `@JsonTest`，请在 `@Before` 方法中调用帮助器的 `initFields` 方法。

如果您使用 Spring Boot 的基于 AssertJ 的帮助器对给定 JSON 路径中的数字值进行断言，则可能无法使用，`isEqualTo`具体取决于类型。相反，您可以使用 AssertJ`satisfies`断言该值与给定条件是否匹配。例如，以下示例断言实际数字是否接近浮点值`0.15`，偏移量`0.01`。

```java
@Test
void someTest() throws Exception {
    SomeObject value = new SomeObject(0.152f);
    assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
            .satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
```

**8.3.13. 自动配置的 Spring MVC 测试**

要测试 Spring MVC 控制器是否按预期工作，请使用`@WebMvcTest`注解。 `@WebMvcTest`自动配置 Spring MVC 基础结构并将扫描的 bean 限制为`@Controller`, `@ControllerAdvice`, `@JsonComponent`, `Converter`, `GenericConverter`, `Filter`, `HandlerInterceptor`, `WebMvcConfigurer`,`WebMvcRegistrations`和`HandlerMethodArgumentResolver`. 使用注解`@WebMvcTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) 启用`@WebMvcTest`的自动配置设置列表。

如果您需要注册额外的组件，例如 Jackson `Module`，您可以通过在测试中使用`@Import`来导入额外的配置类。 通常，`@WebMvcTest`仅限于单个控制器，并与`@MockBean`结合使用，为所需的协作者提供模拟实现。

`@WebMvcTest`也可以自动配置`MockMvc`。Mock MVC 提供了一种强大的方法来快速测试 MVC 控制器，而无需启动完整的 HTTP 服务器。

您还可以在非`@WebMvcTest`中通过使用`@AutoConfigureMockMvc`注解来自动配置`MockMvc`（例如`@SpringBootTest`） 。以下示例使用`MockMvc`：

```java
@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
    }

}
```

如果您需要配置自动配置的元素（例如，当使用 servlet 过滤器时），您可以在`@AutoConfigureMockMvc`注解中使用属性。 如果您使用 HtmlUnit 和 Selenium，自动配置还提供 HtmlUnit `WebClient`bean 和/或 Selenium `WebDriver`bean。以下示例使用 HtmlUnit：

```java
@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new VehicleDetails("Honda", "Civic"));
        HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
        assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
    }

}
```

> 默认情况下，Spring Boot 将`WebDriver`bean 放在一个特殊的“域”中，以确保驱动程序在每次测试后退出并注入新实例。如果您不想要这种行为，您可以添加`@Scope("singleton")`到您的`WebDriver` `@Bean`定义中。 Spring Boot 创建的`webDriver`域将替换任何用户定义的同名域。如果您定义自己的`webDriver`域，您可能会发现在使用`@WebMvcTest`它会停止工作. 如果类路径上有 Spring Security，`@WebMvcTest`也会扫描`WebSecurityConfigurer`bean。您可以使用 Spring Security 的测试支持，而不是完全禁用此类测试的安全性。有关如何使用 Spring Security`MockMvc`支持的更多详细信息，请参见\*[howto.html](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.testing.with-spring-security)\* how-to 部分。

有时编写 Spring MVC 测试是不够的；Spring Boot 可以帮助您[使用实际的服务器运行完整的端到端测试](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-running-server)。

7.&#x39;**.3.14. 自动配置的 Spring WebFlux 测试**

要测试[Spring WebFlux](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/web-reactive.html)控制器是否按预期工作，您可以使用`@WebFluxTest`注解。 `@WebFluxTest`自动配置 Spring WebFlux 基础结构并将扫描的 bean 限制为`@Controller`、`@ControllerAdvice`、`@JsonComponent`、`Converter`、`GenericConverter`、`WebFilter`和`WebFluxConfigurer`. 使用注解`@WebFluxTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。

可以[在附录](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)中找到 启用`@WebFluxTest`的自动配置列表。 如果您需要注册额外的组件，例如 Jackson `Module`，您可以在您的测试中 使用`@Import`导入额外的配置类。 通常，`@WebFluxTest`仅限于单个控制器并与`@MockBean`注解结合使用，为所需的协作者提供模拟实现。

`@WebFluxTest`还自动配置了 [`WebTestClient`](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#webtestclient)，它提供了一种强大的方法来快速测试 WebFlux 控制器，而无需启动完整的 HTTP 服务器。

您还可以在非`@WebFluxTest`中通过使用`@AutoConfigureWebTestClient`注解来自动配置`WebTestClient`（例如`@SpringBootTest`） 。以下示例使用`WebTestClient`和`@WebFluxTest`：

```java
@WebFluxTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private WebTestClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Honda Civic");
    }

}
```

此设置仅受 WebFlux 应用程序支持，因为在模拟应用程序中使用`WebTestClient`目前仅适用于 WebFlux。

`@WebFluxTest`无法检测通过功能性 Web 框架注册的路由。要在上下文中测试`RouterFunction`bean，请考虑通过使用`@Import`或使用`@SpringBootTest`导入您自己的`RouterFunction`。 `@WebFluxTest`无法检测作为`@Bean`类型注册的自定义安全配置`SecurityWebFilterChain`。要将其包含在您的测试中，您需要通过使用`@Import`或使用`@SpringBootTest`导入注册 bean 的配置。 有时编写 Spring WebFlux 测试是不够的；Spring Boot 可以帮助您[使用实际的服务器运行完整的端到端测试](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-running-server)。

7.&#x39;**.3.15。自动配置的 Spring GraphQL 测试**

Spring GraphQL 提供了专门的测试支持模块；您需要将其添加到您的项目中： *Maven*

```xml
<dependencies>
    <dependency>
        <groupId>org.springframework.graphql</groupId>
        <artifactId>spring-graphql-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- Unless already present in the compile scope -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
```

*Gradle*

```gradle
dependencies {
    testImplementation("org.springframework.graphql:spring-graphql-test")
    // Unless already present in the implementation configuration
    testImplementation("org.springframework.boot:spring-boot-starter-webflux")
}
```

这个测试模块附带了[GraphQlTester](https://docs.spring.io/spring-graphql/docs/1.0.1/reference/html//#testing-graphqltester)。测试仪在测试中大量使用，所以一定要熟悉使用它。有了`GraphQlTester`变体，Spring Boot 将根据测试类型自动配置它们：

* `ExecutionGraphQlServiceTester`在服务器端执行测试，没有客户端也没有实际传输
* 使用`HttpGraphQlTester`连接到服务器的客户端执行测试，有或没有服务器都可以

Spring Boot 可帮助您使用`@GraphQlTest`注解测试[Spring GraphQL 控制器](https://docs.spring.io/spring-graphql/docs/1.0.1/reference/html/#controllers)。 `@GraphQlTest`自动配置 Spring GraphQL 基础设施，不涉及任何传输或服务器。这会将扫描的bean 限制为`@Controller`、`RuntimeWiringConfigurer`、`JsonComponent`、`Converter`、`GenericConverter`、`DataFetcherExceptionResolver`、`Instrumentation`和`GraphQlSourceBuilderCustomizer`。使用注解`@GraphQlTest`时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。

可以[在附录](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)中找到`@GraphQlTest` 启用的自动配置列表。

如果您需要注册额外的组件，例如 Jackson `Module`，您可以在您的测试中使用`@Import`导入额外的配置类。 通常，`@GraphQlTest`仅限于一组控制器并与`@MockBean`注解结合使用，为所需的协作者提供模拟实现。

```java
@GraphQlTest(GreetingController.class)
class GreetingControllerTests {

    @Autowired
    private GraphQlTester graphQlTester;

    @Test
    void shouldGreetWithSpecificName() {
        this.graphQlTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String.class)
                .isEqualTo("Hello, Alice!");
    }

    @Test
    void shouldGreetWithDefaultName() {
        this.graphQlTester.document("{ greeting } ").execute().path("greeting").entity(String.class)
                .isEqualTo("Hello, Spring!");
    }

}
```

`@SpringBootTest`测试是完整的集成测试，涉及整个应用程序。当使用随机或定义的端口时，会配置一个实时服务器并自动提供一个`HttpGraphQlTester` bean，以便您可以使用它来测试您的服务器。配置 MOCK 环境后，您还可以通过使用以下命令`@AutoConfigureHttpGraphQlTester` 注解测试类来请求`HttpGraphQlTester` bean ：

```java
@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {

    @Test
    void shouldGreetWithSpecificName(@Autowired HttpGraphQlTester graphQlTester) {
        HttpGraphQlTester authenticatedTester = graphQlTester.mutate()
                .webTestClient(
                        (client) -> client.defaultHeaders((headers) -> headers.setBasicAuth("admin", "ilovespring")))
                .build();
        authenticatedTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String.class)
                .isEqualTo("Hello, Alice!");
    }

}
```

**8.3.16. 自动配置的数据 Cassandra 测试**

您可以使用`@DataCassandraTest`来测试 Cassandra 应用程序。默认情况下，它配置一个`CassandraTemplate`，来扫描`@Table`类，并配置 Spring Data Cassandra 存储库。使用注解`@DataCassandraTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。（有关在 Spring Boot 中使用 Cassandra 的更多信息，请参阅“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.cassandra) ”。）

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)`@DataCassandraTest`启用的自动配置设置列表。

以下示例显示了在 Spring Boot 中使用 Cassandra 测试的典型设置：

```java
@DataCassandraTest
class MyDataCassandraTests {

    @Autowired
    private SomeRepository repository;

}
```

7.&#x39;**.3.17. 自动配置的 Data Couchbase 测试**

您可以使用`@DataCouchbaseTest`来测试 Couchbase 应用程序。默认情况下，它配置一个`CouchbaseTemplate`或者 `ReactiveCouchbaseTemplate`，扫描`@Document`类，并配置 Spring Data Couchbase 存储库。使用注解`@DataCouchbaseTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。（有关在 Spring Boot 中使用 Couchbase 的更多信息，请参阅本章前面的“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.couchbase) ”。）

`@DataCouchbaseTest`可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) 启用的自动配置设置列表。 以下示例显示了在 Spring Boot 中使用 Couchbase 测试的典型设置：

```java
@DataCouchbaseTest
class MyDataCouchbaseTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
```

7.&#x39;**.3.18. 自动配置的数据 Elasticsearch 测试**

您可以使用`@DataElasticsearchTest`来测试 Elasticsearch 应用程序。默认情况下，它配置一个`ElasticsearchRestTemplate`，扫描`@Document`类，并配置 Spring Data Elasticsearch 存储库。使用注解`@DataElasticsearchTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。（有关在 Spring Boot 中使用 Elasticsearch 的更多信息，请参阅本章前面的“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.elasticsearch) ”。）

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@DataElasticsearchTest`启用的自动配置设置列表。 以下示例显示了在 Spring Boot 中使用 Elasticsearch 测试的典型设置：

```java
@DataElasticsearchTest
class MyDataElasticsearchTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
```

7.&#x39;**.3.19. 自动配置的数据 JPA 测试**

您可以使用`@DataJpaTest`注解来测试 JPA 应用程序。默认情况下，它会扫描`@Entity`类并配置 Spring Data JPA 存储库。如果类路径上有一个嵌入式数据库，它也会配置一个。默认情况下，通过将`spring.jpa.show-sql`属性设置为`true`来记录 SQL 查询。这可以使用`showSql()`注解的属性来禁用。

使用注解`@DataJpaTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties` bean。

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@DataJpaTest`启用的自动配置设置列表。 默认情况下，数据 JPA 测试是事务性的，并在每个测试结束时回滚。有关更多详细信息，请参阅 Spring Framework 参考文档中的[相关部分](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#testcontext-tx-enabling-transactions)。如果这不是您想要的，您可以为测试或整个班级禁用事务管理，如下所示：

```java
@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {

    // ...

}
```

数据 JPA 测试也可以注入一个bean，它提供了一个专门为测试设计[`TestEntityManager`](https://github.com/spring-projects/spring-boot/tree/v2.7.3/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/orm/jpa/TestEntityManager.java)的标准 JPA`EntityManager` 的替代方案。 `TestEntityManager`也可以通过添加`@AutoConfigureTestEntityManager`自动配置到任何基于 Spring 的测试类。这样做时，请确保您的测试在事务中运行，例如通过添加`@Transactional`到您的测试类或方法。 如果您需要，也可以使用`JdbcTemplate`。以下示例显示了`@DataJpaTest`正在使用的注解：

```java
@DataJpaTest
class MyRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    void testExample() {
        this.entityManager.persist(new User("sboot", "1234"));
        User user = this.repository.findByUsername("sboot");
        assertThat(user.getUsername()).isEqualTo("sboot");
        assertThat(user.getEmployeeNumber()).isEqualTo("1234");
    }

}
```

内存嵌入式数据库通常适用于测试，因为它们速度快且不需要任何安装。但是，如果您更喜欢针对真实数据库运行测试，则可以使用`@AutoConfigureTestDatabase`注解，如以下示例所示：

```java
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {

    // ...

}
```

7.&#x39;**.3.20. 自动配置的 JDBC 测试**

`@JdbcTest`类似于`@DataJpaTest`但适用于只需要一个`DataSource`并且不使用 Spring Data JDBC 的测试。默认情况下，它配置一个内存嵌入式数据库和一个`JdbcTemplate`. 使用注解 `@JdbcTest`时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties` bean。

可以[在附录](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)中找到`@JdbcTest` 启用的自动配置列表。 默认情况下，JDBC 测试是事务性的，并在每次测试结束时回滚。有关更多详细信息，请参阅 Spring Framework 参考文档中的[相关部分](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#testcontext-tx-enabling-transactions)。如果这不是您想要的，您可以为测试或整个班级禁用事务管理，如下所示：

```java
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests {

}
```

如果您希望您的测试针对真实数据库运行，您可以使用与`DataJpaTest`相同的方式使用注解`@AutoConfigureTestDatabase` 。（请参阅“[自动配置的数据 JPA 测试](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-spring-data-jpa)”。）

7.&#x39;**.3.21. 自动配置的数据 JDBC 测试**

`@DataJdbcTest`类似于`@JdbcTest`但适用于使用 Spring Data JDBC 存储库的测试。默认情况下，它配置内存中的嵌入式数据库、`JdbcTemplate`和 Spring Data JDBC 存储库。使用`@DataJdbcTest`注解时只`AbstractJdbcConfiguration`扫描子类，不扫描常规`@Component`和`@ConfigurationProperties`bean。 `@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。

可以[在附录](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)中找到`@DataJdbcTest`启用的自动配置列表。 默认情况下，Data JDBC 测试是事务性的，并在每次测试结束时回滚。有关更多详细信息，请参阅 Spring Framework 参考文档中的[相关部分](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#testcontext-tx-enabling-transactions)。如果这不是您想要的，您可以禁用测试或整个测试类的事务管理，如[JDBC 示例中所示](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-jdbc)。

如果您希望您的测试针对真实数据库运行，您可以使用与`DataJpaTest`相同的方式使用注解`@AutoConfigureTestDatabase` 。（请参阅“[自动配置的数据 JPA 测试](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-spring-data-jpa)”。）

7.&#x39;**.3.22。自动配置的 jOOQ 测试**

您可以使用与`@JdbcTest`相似的方式使用`@JooqTest`进行jOOQ 相关测试。由于 jOOQ 严重依赖与数据库模式相对应的基于 Java 的模式，因此会使用现有`DataSource`。如果您想用内存数据库替换它，您可以使用`@AutoConfigureTestDatabase`覆盖这些设置。（有关在 Spring Boot 中使用 jOOQ 的更多信息，请参阅“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.sql.jooq) ”。）使用注解`@JooqTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。

可以[在附录](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)中找到`@JooqTest` 启用的自动配置列表。 `@JooqTest`配置一个`DSLContext`. 以下示例显示了`@JooqTest`正在使用的注解：

```java
@JooqTest
class MyJooqTests {

    @Autowired
    private DSLContext dslContext;

    // ...

}
```

JOOQ 测试是事务性的，默认情况下会在每个测试结束时回滚。如果这不是您想要的，您可以禁用测试或整个测试类的事务管理，如[JDBC 示例中所示](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.autoconfigured-jdbc)。

7.&#x39;**.3.23. 自动配置数据 MongoDB 测试**

您可以使用`@DataMongoTest`来测试 MongoDB 应用程序。默认情况下，它配置一个内存中的嵌入式 MongoDB（如果可用），配置一个`MongoTemplate`，扫描`@Document`类，并配置 Spring Data MongoDB 存储库。使用注解`@DataMongoTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。（有关在 Spring Boot 中使用 MongoDB 的更多信息，请参阅“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.mongodb) ”。）

`@DataMongoTest`可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) 启用的自动配置设置列表。 下面的类显示了`@DataMongoTest`正在使用的注解：

```java
@DataMongoTest
class MyDataMongoDbTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    // ...

}
```

内存中嵌入式 MongoDB 通常适用于测试，因为它速度快且不需要任何开发人员安装。但是，如果您更喜欢针对真正的 MongoDB 服务器运行测试，则应排除嵌入式 MongoDB 自动配置，如以下示例所示：

```java
@DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class)
class MyDataMongoDbTests {

    // ...

}
```

7.&#x39;**.3.24. 自动配置的数据 Neo4j 测试**

`@DataNeo4jTest`用来测试 Neo4j 应用程序。默认情况下，它会扫描`@Node`类并配置 Spring Data Neo4j 存储库。使用注解 `@DataNeo4jTest`时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。（有关在 Spring Boot 中使用 Neo4J 的更多信息，请参阅“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.neo4j) ”。）

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@DataNeo4jTest`启用的自动配置设置列表。 以下示例显示了在 Spring Boot 中使用 Neo4J 测试的典型设置：

```java
@DataNeo4jTest
class MyDataNeo4jTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
```

默认情况下，Data Neo4j 测试是事务性的，并在每次测试结束时回滚。有关更多详细信息，请参阅 Spring Framework 参考文档中的[相关部分](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/testing.html#testcontext-tx-enabling-transactions)。如果这不是您想要的，您可以为测试或整个类禁用事务管理，如下所示：

```java
@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests {

}
```

反应式访问不支持事务测试。如果您使用此样式，则必须按上述方式配置`@DataNeo4jTest`测试。

7.&#x39;**.3.25. 自动配置的数据 Redis 测试**

`@DataRedisTest`用来测试 Redis 应用程序。默认情况下，它会扫描`@RedisHash`类并配置 Spring Data Redis 存储库。使用注解`@DataRedisTest`时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。。（有关在 Spring Boot 中使用 Redis 的更多信息，请参阅“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.redis) ”。）

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@DataRedisTest`启用的自动配置设置列表。 以下示例显示了`@DataRedisTest`正在使用的注解：

```java
@DataRedisTest
class MyDataRedisTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
```

7.&#x39;**.3.26. 自动配置的数据 LDAP 测试**

您可以使用`@DataLdapTest`来测试 LDAP 应用程序。默认情况下，它配置内存中的嵌入式 LDAP（如果可用）、配置`LdapTemplate`、扫描`@Entry`类并配置 Spring Data LDAP 存储库。使用注解 `@DataLdapTest`时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。（有关在 Spring Boot 中使用 LDAP 的更多信息，请参阅“ [data.html](https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.ldap) ”。）

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@DataLdapTest`启用的自动配置设置列表。 以下示例显示了`@DataLdapTest`正在使用的注解：

```java
@DataLdapTest
class MyDataLdapTests {

    @Autowired
    private LdapTemplate ldapTemplate;

    // ...

}
```

内存中嵌入式 LDAP 通常适用于测试，因为它速度快且不需要任何开发人员安装。但是，如果您更喜欢针对真实的 LDAP 服务器运行测试，则应排除嵌入式 LDAP 自动配置，如以下示例所示：

```java
@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
class MyDataLdapTests {

    // ...

}
```

7.&#x39;**.3.27. 自动配置的 REST 客户端**

您可以使用`@RestClientTest`注解来测试 REST 客户端。默认情况下，它会自动配置 Jackson、GSON 和 Jsonb 支持，配置`RestTemplateBuilder`并添加`MockRestServiceServer`. 使用注解`@RestClientTest` 时不会扫描常规`@Component`和`@ConfigurationProperties`bean。`@EnableConfigurationProperties`可用于包含`@ConfigurationProperties`bean。

可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@RestClientTest`启用的自动配置设置列表。 您要测试的特定 bean 应使用`@RestClientTest` 的`value`或`components`属性指定，如以下示例所示：

```java
@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {

    @Autowired
    private RemoteVehicleDetailsService service;

    @Autowired
    private MockRestServiceServer server;

    @Test
    void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
        this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
        String greeting = this.service.callRestService();
        assertThat(greeting).isEqualTo("hello");
    }

}
```

7.&#x39;**.3.28. 自动配置的 Spring REST 文档测试**

您可以使用`@AutoConfigureRestDocs`注解在您的测试中使用[Spring REST Docs](https://spring.io/projects/spring-restdocs)与 Mock MVC、REST Assured 或 WebTestClient。它消除了 Spring REST Docs 中对 JUnit 扩展的需求。

`@AutoConfigureRestDocs`可用于覆盖默认输出目录（如果您使用 Maven 默认是`target/generated-snippets`或使用 Gradle默认为`build/generated-snippets`）。它还可用于配置出现在任何记录的 URI 中的主机、方案和端口。

**使用 Mock MVC 自动配置 Spring REST 文档测试**

在测试基于 servlet 的 Web 应用程序时可以使用`@AutoConfigureRestDocs`自定义`MockMvc`bean 以使用 Spring REST Docs。您可以像使用 Mock MVC 和 Spring REST Docs 时一样在测试中使用和使用它，如以下示例所示：

```java
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void listUsers() throws Exception {
        this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andDo(document("list-users"));
    }

}
```

如果您需要对 Spring REST Docs 配置的控制比`@AutoConfigureRestDocs`的属性提供的更多，则可以使用`RestDocsMockMvcConfigurationCustomizer`bean，如以下示例所示：

```java
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {

    @Override
    public void customize(MockMvcRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}
```

如果你想利用 Spring REST Docs 对参数化输出目录的支持，你可以创建一个`RestDocumentationResultHandler`bean。使用此结果处理程序进行自动配置调用`alwaysDo`，从而导致每个`mockMvc`调用自动生成默认片段。下面的例子显示了一个被定义的`RestDocumentationResultHandler`：

```java
@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {

    @Bean
    public RestDocumentationResultHandler restDocumentation() {
        return MockMvcRestDocumentation.document("{method-name}");
    }

}
```

**使用 WebTestClient 自动配置 Spring REST 文档测试**

也可以在测试响应式 Web 应用程序的`WebTestClient`时使用`@AutoConfigureRestDocs`。您可以像使用 Spring REST Docs 时一样在测试中使用`@WebFluxTest`和使用`@Autowired`，如以下示例所示：

```java
@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void listUsers() {
        this.webTestClient
            .get().uri("/")
        .exchange()
        .expectStatus()
            .isOk()
        .expectBody()
            .consumeWith(document("list-users"));
    }

}
```

如果您需要对 Spring REST Docs 配置的控制比 的属性提供的更多`@AutoConfigureRestDocs`，则可以使用`RestDocsWebTestClientConfigurationCustomizer`bean，如以下示例所示：

```java
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer {

    @Override
    public void customize(WebTestClientRestDocumentationConfigurer configurer) {
        configurer.snippets().withEncoding("UTF-8");
    }

}
```

如果您想利用 Spring REST Docs 对参数化输出目录的支持，您可以使用 `WebTestClientBuilderCustomizer`为每个实体交换结果配置消费者。以下示例显示了这样的`WebTestClientBuilderCustomizer`定义：

```java
@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {

    @Bean
    public WebTestClientBuilderCustomizer restDocumentation() {
        return (builder) -> builder.entityExchangeResultConsumer(document("{method-name}"));
    }

}
```

**带有 REST Assured 的自动配置 Spring REST 文档测试**

`@AutoConfigureRestDocs`使预配置为使用 Spring REST Docs 的`RequestSpecification` bean 可用于您的测试。您可以像使用 REST Assured 和 Spring REST Docs 时一样在测试中使用`@Autowired`和使用它，如以下示例所示：

```java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Test
    void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) {
        given(documentationSpec)
            .filter(document("list-users"))
        .when()
            .port(port)
            .get("/")
        .then().assertThat()
            .statusCode(is(200));
    }

}
```

如果您需要对 Spring REST Docs 配置的控制比`@AutoConfigureRestDocs`的属性提供的更多，则可以使用`RestDocsRestAssuredConfigurationCustomizer` bean，如以下示例所示：

```java
@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer {

    @Override
    public void customize(RestAssuredRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}
```

7.&#x39;**.3.29. 自动配置的 Spring Web 服务测试**

**自动配置的 Spring Web 服务客户端测试**

您可以使用 `@WebServiceClientTest` 来测试使用 Spring Web Services 项目实现 Web 服务的应用程序。默认情况下，它配置一个模拟`WebServiceServer`bean 并自动自定义您的`WebServiceTemplateBuilder`. （有关在 Spring Boot 中使用 Web 服务的更多信息，请参阅“ [io.html](https://docs.spring.io/spring-boot/docs/current/reference/html/io.html#io.webservices) ”。） 可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration)`@WebServiceClientTest` 启用的自动配置设置列表。 以下示例显示了`@WebServiceClientTest`正在使用的注解：

```java
@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {

    @Autowired
    private MockWebServiceServer server;

    @Autowired
    private SomeWebService someWebService;

    @Test
    void mockServerCall() {
        this.server
            .expect(payload(new StringSource("<request/>")))
            .andRespond(withPayload(new StringSource("<response><status>200</status></response>")));
        assertThat(this.someWebService.test())
            .extracting(Response::getStatus)
            .isEqualTo(200);
    }

}
```

**自动配置的 Spring Web 服务服务器测试**

您可以使用 `@WebServiceServerTest` 来测试使用 Spring Web Services 项目实现 Web 服务的应用程序。默认情况下，它配置一个可用于调用 Web 服务端点的 `MockWebServiceClient`bean。（有关在 Spring Boot 中使用 Web 服务的更多信息，请参阅“ [io.html](https://docs.spring.io/spring-boot/docs/current/reference/html/io.html#io.webservices) ”。） 可[在附录中找到](https://docs.spring.io/spring-boot/docs/current/reference/html/test-auto-configuration.html#appendix.test-auto-configuration) `@WebServiceServerTest`启用的自动配置设置列表。 以下示例显示了`@WebServiceServerTest`正在使用的注解：

```java
@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {

    @Autowired
    private MockWebServiceClient client;

    @Test
    void mockServerCall() {
        this.client
            .sendRequest(RequestCreators.withPayload(new StringSource("<ExampleRequest/>")))
            .andExpect(ResponseMatchers.payload(new StringSource("<ExampleResponse>42</ExampleResponse>")));
    }

}
```

7.&#x39;**.3.30. 额外的自动配置和切面**

每个切面提供一个或多个`@AutoConfigure…`注解，即定义应作为切面一部分包含的自动配置。通过创建自定义`@AutoConfigure…`注解或通过`@ImportAutoConfiguration`添加到测试中，可以逐个测试添加其他自动配置，如以下示例所示：

```java
@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests {

}
```

确保不要使用常规`@Import`注解来导入自动配置，因为它们是由 Spring Boot 以特定方式处理的。 或者，可以为切面注解的任何使用添加额外的自动配置，方法是将它们注册到存储的`META-INF/spring`文件中，如以下示例所示： META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports

```
com.example.IntegrationAutoConfiguration
```

在此示例中，在`com.example.IntegrationAutoConfiguration`使用 注解的每个测试上都启用了`@JdbcTest`。

您可以通过在此文件中使用`#`注解。

切片或@AutoConfigure ...注释可以通过这种方式自定义，只要它使用@ImportAutoConfiguration进行元注释即可。

7.&#x39;**.3.31. 用户配置和切面**

如果您以合理的方式[构建代码，则](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.structuring-your-code)[默认情况下](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.detecting-configuration)您的`@SpringBootApplication`类将用作测试的配置。

然后，重要的是不要在应用程序的主类中乱扔特定于其功能特定区域的配置设置。

假设您使用的是 Spring Batch，并且您依赖于它的自动配置。您可以定义`@SpringBootApplication`如下：

```java
@SpringBootApplication
@EnableBatchProcessing
public class MyApplication {

    // ...

}
```

因为这个类是测试的源配置，所以任何切面测试实际上都会尝试启动 Spring Batch，这绝对不是你想要做的。推荐的方法是将特定于区域的配置移动到与您的应用程序处于同一级别的单独`@Configuration`类中，如以下示例所示：

```java
@Configuration(proxyBeanMethods = false)
@EnableBatchProcessing
public class MyBatchConfiguration {

    // ...

}
```

根据您的应用程序的复杂性，您可能有一个用于自定义的类或每个域区域一个`@Configuration`类。后一种方法允许您在其中一个测试中启用它，如有必要，使用`@Import`注解。有关何时可能希望为切面测试启用特定类的 `@Configuration`更多详细信息，请参阅[此操作指南部分。](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.testing.slice-tests) 测试切面将`@Configuration`类排除在扫描之外。例如，对于 `@WebMvcTest`，以下配置不会在测试切面加载的应用程序上下文中包含给定的`WebMvcConfigurer` bean：

```java
@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {

    @Bean
    public WebMvcConfigurer testConfigurer() {
        return new WebMvcConfigurer() {
            // ...
        };
    }

}
```

但是，下面的配置将导致自定义`WebMvcConfigurer`被测试切面加载。

```java
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    // ...

}
```

另一个混淆来源是类路径扫描。假设，当您以合理的方式构建代码时，您需要扫描一个额外的包。您的应用程序可能类似于以下代码：

```java
@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {

    // ...

}
```

这样做有效地覆盖了默认的组件扫描指令，其副作用是扫描这两个包，而不管您选择的切面是什么。例如，a`@DataJpaTest`似乎突然扫描应用程序的组件和用户配置。同样，将自定义指令移动到单独的类是解决此问题的好方法。

如果这不是您的选择，您可以`@SpringBootConfiguration`在测试的层次结构中创建一个位置，以便使用它。或者，您可以为您的测试指定一个源，这将禁用查找默认源的行为。

7.&#x39;**.3.32. 使用 Spock 测试 Spring Boot 应用程序**

Spock 2.x 可用于测试 Spring Boot 应用程序。为此，请将 Spock`spock-spring`模块的依赖项添加到应用程序的构建中。 `spock-spring`将 Spring 的测试框架集成到 Spock 中。有关详细信息，请参阅[Spock 的 Spring 模块的文档](https://spockframework.org/spock/docs/2.0/modules.html#_spring_module)。


# 7.9.4. 测试容器

[Testcontainers](https://www.testcontainers.org/)库提供了一种管理 Docker 容器内运行的服务的方法。它与 JUnit 集成，允许您编写一个测试类，该测试类可以在任何测试运行之前启动容器。Testcontainers 对于编写与真实后端服务（例如 MySQL、MongoDB、Cassandra 等）通信的集成测试特别有用。

测试容器可以在 Spring Boot 测试中使用，如下所示：

```
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
​
    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
​
    @Test
    void myTest() {
        // ...
    }
​
}
```

这将在运行任何测试之前启动一个运行 Neo4j 的 docker 容器（如果 Docker 在本地运行）。在大多数情况下，您需要配置应用程序以连接到容器中运行的服务。

**服务连接**

服务连接是与任何远程服务的连接。Spring Boot 的自动配置可以使用服务连接的详细信息，并使用它们来建立与远程服务的连接。执行此操作时，连接详细信息优先于任何与连接相关的配置属性。

使用测试容器时，可以通过注释测试类中的容器字段来自动为容器中运行的服务创建连接详细信息。

```
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
​
    @Container
    @ServiceConnection
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
​
    @Test
    void myTest() {
        // ...
    }
​
}
```

由于`@ServiceConnection`，上述配置允许应用程序中与 Neo4j 相关的 bean 与 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。这是通过自动定义一个`Neo4jConnectionDetails`bean 来完成的，然后由 Neo4j 自动配置使用该 bean，覆盖任何与连接相关的配置属性。

> 您需要将该`spring-boot-testcontainers`模块添加为测试依赖项，以便将服务连接与测试容器一起使用。

服务连接注解由注册到`spring.factories` 的 `ContainerConnectionDetailsFactory` 类处理。 `ContainerConnectionDetailsFactory` 可以基于特定的 `Container` 子类或 `Docker` 映像名称创建 `ConnectionDetails`bean。

`spring-boot-testcontainers`jar中提供了以下服务连接工厂：

| 连接详情                             | 匹配于                                                                                                      |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `ActiveMQConnectionDetails`      | 名为“symptoma/activemq”的容器                                                                                 |
| `CassandraConnectionDetails`     | 容器类型`CassandraContainer`                                                                                 |
| `CouchbaseConnectionDetails`     | 容器类型`CouchbaseContainer`                                                                                 |
| `ElasticsearchConnectionDetails` | 容器类型`ElasticsearchContainer`                                                                             |
| `FlywayConnectionDetails`        | 容器类型`JdbcDatabaseContainer`                                                                              |
| `JdbcConnectionDetails`          | 容器类型`JdbcDatabaseContainer`                                                                              |
| `KafkaConnectionDetails`         | 类型`KafkaContainer`或`RedpandaContainer`的容器                                                                |
| `LiquibaseConnectionDetails`     | 容器类型`JdbcDatabaseContainer`                                                                              |
| `MongoConnectionDetails`         | 容器类型`MongoDBContainer`                                                                                   |
| `Neo4jConnectionDetails`         | 容器类型`Neo4jContainer`                                                                                     |
| `OtlpMetricsConnectionDetails`   | 名为“otel/opentelemetry-collector-contrib”的容器                                                              |
| `OtlpTracingConnectionDetails`   | 名为“otel/opentelemetry-collector-contrib”的容器                                                              |
| `PulsarConnectionDetails`        | 容器类型`PulsarContainer`                                                                                    |
| `R2dbcConnectionDetails`         | 类型为`MariaDBContainer`、`MSSQLServerContainer`、`MySQLContainer`、`OracleContainer`或`PostgreSQLContainer`的容器 |
| `RabbitConnectionDetails`        | 容器类型`RabbitMQContainer`                                                                                  |
| `RedisConnectionDetails`         | 名为“redis”的容器                                                                                             |
| `ZipkinConnectionDetails`        | 名为“openzipkin/zipkin”的容器                                                                                 |

> 默认情况下，将为给定的`Container`创建所有适用的连接详细信息 bean 。例如， `PostgreSQLContainer`将创建`JdbcConnectionDetails`和`R2dbcConnectionDetails`。
>
> 如果您只想创建适用类型的子集，则可以使用`type`的属性`@ServiceConnection`。

默认情况下`Container.getDockerImageName()`用于获取用于查找连接详细信息的名称。只要 Spring Boot 能够获取`Container` 的实例，这种情况就有效，就像使用上面示例中的`static`字段时的情况一样。

如果您使用`@Bean`方法，Spring Boot 将不会调用 bean 方法来获取 Docker 映像名称，因为这会导致急切的初始化问题。相反，bean 方法的返回类型用于找出应使用哪个连接详细信息。只要您使用类型化容器（例如`Neo4jContainer`或 `RabbitMQContainer`） ，此方法就有效。如果您正在使用（例如 Redis），`GenericContainer`将停止工作，如以下示例所示：

```
@TestConfiguration(proxyBeanMethods = false)
public class MyRedisConfiguration {
​
    @Bean
    @ServiceConnection(name = "redis")
    public GenericContainer<?> redisContainer() {
        return new GenericContainer<>("redis:7");
    }
​
}
```

Spring Boot 无法判断`GenericContainer`使用的是哪个容器映像，因此必须使用`@ServiceConnection`的`name` 属性来提供该提示。

您还可以使用`@ServiceConnection` 的`name`属性来覆盖将使用的连接详细信息，例如在使用自定义图像时。如果您使用 Docker 映像`registry.mycompany.com/mirror/myredis`，则需要`@ServiceConnection(name="redis")`确保`RedisConnectionDetails`已创建。

**动态属性**

服务连接的一个稍微详细但也更灵活的替代方案是`@DynamicPropertySource`。静态`@DynamicPropertySource`方法允许向 Spring 环境添加动态属性值。

```
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
​
    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
​
    @Test
    void myTest() {
        // ...
    }
​
    @DynamicPropertySource
    static void neo4jProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
    }
​
}
```

上述配置允许应用程序中与 Neo4j 相关的 bean 与 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。


# 7.9.5. 测试工具

测试应用程序时通常有用的一些测试实用程序类被打包为`spring-boot`.

**7.9.5.1. ConfigDataApplicationContextInitializer**

`ConfigDataApplicationContextInitializer`是一个`ApplicationContextInitializer`你可以应用到你的测试来加载 Spring Boot`application.properties`文件的工具。当您不需要 提供的全套功能时，您可以使用它，`@SpringBootTest`如下例所示：

```java
@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {

    // ...

}
```

单独使用`ConfigDataApplicationContextInitializer`不支持`@Value("${…}")`注入。它唯一的工作是确保将`application.properties`文件加载到 Spring 的`Environment`. 为了获得`@Value`支持，您需要另外配置 `PropertySourcesPlaceholderConfigurer`或 `@SpringBootTest`，它会为您自动配置一个。

**7.9.5.2. 测试属性值**

`TestPropertyValues`让您快速将属性添加到 `ConfigurableEnvironment`或`ConfigurableApplicationContext`. 您可以使用`key=value`字符串调用它，如下所示：

```java
class MyEnvironmentTests {

    @Test
    void testPropertySources() {
        MockEnvironment environment = new MockEnvironment();
        TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
        assertThat(environment.getProperty("name")).isEqualTo("Boot");
    }

}
```

**7.9.5.3. 输出捕获**

`OutputCapture`是一个可用于捕获`System.out`和`System.err`输出的 JUnit `Extension`。要使用它，需要添加`@ExtendWith(OutputCaptureExtension.class)`和 injection`CapturedOutput`作为测试类构造函数或测试方法的参数，如下所示：

```java
@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {

    @Test
    void testName(CapturedOutput output) {
        System.out.println("Hello World!");
        assertThat(output).contains("World");
    }

}
```

**7.9.5.4. 测试RestTemplate**

`TestRestTemplate`是 Spring 的一种方便替代方案，`RestTemplate`在集成测试中很有用。您可以获得一个普通模板或发送基本 HTTP 身份验证（带有用户名和密码）的模板。在任何一种情况下，模板都是容错的。这意味着它以一种测试友好的方式运行，不会在 4xx 和 5xx 错误上抛出异常。相反，可以通过返回`ResponseEntity`的及其状态码检测此类错误。

Spring Framework 5.0 提供了一个新功能`WebTestClient`，适用于[WebFlux 集成测试](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.spring-webflux-tests)以及[WebFlux 和 MVC 端到端测试](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.with-running-server)。与`TestRestTemplate`不同，它提供了流式的api. 建议使用 Apache HTTP 客户端（版本 4.3.2 或更高版本），但不是强制性的。如果您的类路径中有它，则通过适当地配置客户端来或的`TestRestTemplate`响应。如果您确实使用 Apache 的 HTTP 客户端，则会启用一些额外的测试友好功能：

* 不遵循重定向（因此您可以断言响应位置）。
* Cookie 被忽略（因此模板是无状态的）。

`TestRestTemplate`可以在集成测试中直接实例化，如下例所示：

```java
class MyTests {

    private final TestRestTemplate template = new TestRestTemplate();

    @Test
    void testRequest() {
        ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
        assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
    }

}
```

或者，如果您将`@SpringBootTest`注解与`WebEnvironment.RANDOM_PORT`或`WebEnvironment.DEFINED_PORT`一起使用，您可以注入一个完全配置的`TestRestTemplate`并开始使用它。如有必要，可以通过`RestTemplateBuilder`bean 应用额外的定制。任何未指定主机和端口的 URL 都会自动连接到嵌入式服务器，如下例所示：

```java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests {

    @Autowired
    private TestRestTemplate template;

    @Test
    void testRequest() {
        HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
        assertThat(headers.getLocation()).hasHost("other.example.com");
    }

    @TestConfiguration(proxyBeanMethods = false)
    static class RestTemplateBuilderConfiguration {

        @Bean
        RestTemplateBuilder restTemplateBuilder() {
            return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
                    .setReadTimeout(Duration.ofSeconds(1));
        }

    }

}
```


# 7.10. Docker Compose 支持

Docker Compose 是一种流行的技术，可用于定义和管理应用程序所需服务的多个容器。通常会在应用程序旁边创建一个`compose.yml`文件，用于定义和配置服务容器。

Docker Compose 的典型工作流程是运行`docker compose up`，处理您的应用程序并连接到已启动的服务，然后在完成后运行`docker compose down`。

`spring-boot-docker-compose`模块可以包含在项目中，为使用 Docker Compose 处理容器提供支持。将模块依赖项添加到您的构建中，如以下 Maven 和 Gradle 清单所示：

Maven

```
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-docker-compose</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
```

Gradle

```
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}
```

> `docker-compose`或`docker compose` CLI 应用程序需要位于您的路径上，以便 Spring Boot 的支持正常工作 。

当此模块作为依赖项包含时，Spring Boot 将执行以下操作：

* 在应用程序目录中搜索`compose.yml`和其他常见的撰写文件名
* 使用找到的`compose.yml`调用`docker compose up`
* 为每个受支持的容器创建服务连接 Bean
* 当应用程序关闭时调用`docker compose stop`

如果启动应用程序时 Docker Compose 服务已经在运行，Spring Boot 将仅为每个支持的容器创建服务连接 Bean。它不会再次调用`docker compose up`，并且当应用程序关闭时也不会调用`docker compose stop`。

> 默认情况下，Spring Boot 的 Docker Compose 支持在运行测试时处于禁用状态。要启用它，请设置`spring.docker.compose.skip.in-tests`为`false`。

**7.10.1. 服务连接**

服务连接是与任何远程服务的连接。Spring Boot 的自动配置可以使用服务连接的详细信息，并使用它们来建立与远程服务的连接。执行此操作时，连接详细信息优先于任何与连接相关的配置属性。

当使用 Spring Boot 的 Docker Compose 支持时，服务连接将建立到容器映射的端口。

> Docker compose 通常的使用方式是将容器内的端口映射到计算机上的临时端口。例如，Postgres 服务器可能使用端口 5432 在容器内运行，但在本地映射到完全不同的端口。服务连接将始终发现并使用本地映射的端口。

通过容器的镜像名称建立服务连接。目前支持以下服务连接：

| 连接详情                             | 匹配于                                                                                      |
| -------------------------------- | ---------------------------------------------------------------------------------------- |
| `ActiveMQConnectionDetails`      | 名为“symptoma/activemq”的容器                                                                 |
| `CassandraConnectionDetails`     | 名为“cassandra”的容器                                                                         |
| `ElasticsearchConnectionDetails` | 名为“elasticsearch”的容器                                                                     |
| `JdbcConnectionDetails`          | 名为“gvenzl/oracle-free”、“gvenzl/oracle-xe”、“mariadb”、“mssql/server”、“mysql”或“postgres”的容器 |
| `MongoConnectionDetails`         | 名为“mongo”的容器                                                                             |
| `Neo4jConnectionDetails`         | 名为“neo4j”的容器                                                                             |
| `OtlpMetricsConnectionDetails`   | 名为“otel/opentelemetry-collector-contrib”的容器                                              |
| `OtlpTracingConnectionDetails`   | 名为“otel/opentelemetry-collector-contrib”的容器                                              |
| `PulsarConnectionDetails`        | 名为“apachepulsar/pulsar”的容器                                                               |
| `R2dbcConnectionDetails`         | 名为“gvenzl/oracle-free”、“gvenzl/oracle-xe”、“mariadb”、“mssql/server”、“mysql”或“postgres”的容器 |
| `RabbitConnectionDetails`        | 名为“rabbitmq”的容器                                                                          |
| `RedisConnectionDetails`         | 名为“redis”的容器                                                                             |
| `ZipkinConnectionDetails`        | 名为“openzipkin/zipkin”的容器。                                                                |

**7.10.2. 自定义镜像**

有时您可能需要使用您自己的镜像版本来提供服务。您可以使用任何自定义镜像，只要其行为方式与标准镜像相同即可。具体来说，标准映像支持的任何环境变量也必须在您的自定义映像中使用。

如果您的映像使用不同的名称，您可以在`compose.yml`文件中使用标签，以便 Spring Boot 可以提供服务连接。使用名为的标签`org.springframework.boot.service-connection`来提供服务名称。

例如:

```
services:
  redis:
    image: 'mycompany/mycustomredis:7.0'
    ports:
      - '6379'
    labels:
      org.springframework.boot.service-connection: redis
```

**7.10.3. 跳过特定容器**

如果您的`compose.yml`定义了一个容器映像，并且不想连接到您的应用程序，则可以使用标签来忽略它。任何带有`org.springframework.boot.ignore` 标签的容器都会被 Spring Boot 忽略。

例如：

```
services:
  redis:
    image: 'redis:7.0'
    ports:
      - '6379'
    labels:
      org.springframework.boot.ignore: true
```

**7.10.4. 使用特定的撰写文件**

如果您的撰写文件与应用程序不在同一目录中，或者名称不同，则可以在您的`application.yaml` 中使用`application.properties`或`spring.docker.compose.file`来指向不同的文件。属性可以定义为精确路径或与应用程序相关的路径。

例如：

```
spring.docker.compose.file=../my-compose.yml
```

**7.10.5. 等待容器准备就绪**

由 Docker Compose 启动的容器可能需要一些时间才能完全准备好。检查准备情况的建议方法是在`compose.yml`文件中的服务定义下添加一个`healthcheck`部分。

由于从`compose.yml`文件中省略`healthcheck`配置的情况并不少见，因此 Spring Boot 还会直接检查服务准备情况。默认情况下，当可以建立到容器映射端口的 TCP/IP 连接时，容器被视为准备就绪。

您可以通过在`compose.yml`文件中添加`org.springframework.boot.readiness-check.tcp.disable`标签来针对每个容器禁用此功能。

例如：

```
services:
  redis:
    image: 'redis:7.0'
    ports:
      - '6379'
    labels:
      org.springframework.boot.readiness-check.tcp.disable: true
```

您还可以更改`application.properties`或`application.yaml`文件中的超时值：

```
spring.docker.compose.readiness.tcp.connect-timeout=10s
spring.docker.compose.readiness.tcp.read-timeout=5s
```

总体超时可以使用`spring.docker.compose.readiness.timeout`配置。

**7.10.6. 控制 Docker Compose 生命周期**

默认情况下，Spring Boot在应用程序启动调用`docker compose up`和关闭时调用`docker compose stop`。如果您希望有不同的生命周期管理，您可以使用`spring.docker.compose.lifecycle-management`属性。

支持以下值：

* `none`- 不要启动或停止 Docker Compose
* `start-only`- 在应用程序启动时启动 Docker Compose 并使其保持运行
* `start-and-stop`- 当应用程序启动时启动 Docker Compose，并在 JVM 退出时停止它

此外，您可以使用`spring.docker.compose.start.command`属性来更改是否使用`docker compose up`或`docker compose start`。`spring.docker.compose.stop.command`允许您配置是否使用`docker compose down`或`docker compose stop`。

以下示例显示了如何配置生命周期管理：

```
spring.docker.compose.lifecycle-management=start-and-stop
spring.docker.compose.start.command=start
spring.docker.compose.stop.command=down
spring.docker.compose.stop.timeout=1m
```

**7.10.7. 激活 Docker Compose 配置文件**

Docker Compose 配置文件与 Spring 配置文件类似，它们允许您针对特定环境调整 Docker Compose 配置。如果您想激活特定的 Docker Compose 配置文件，您可以使用您的`application.properties`或`application.yaml`文件中的`spring.docker.compose.profiles.active`属性：

```
spring.docker.compose.profiles.active=myprofile
```


# 7.11. 测试容器支持

除了[使用测试容器进行集成测试](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers)之外，还可以在开发时使用它们。下一节将提供更多相关细节。

**7.11.1. 在开发时使用测试容器**

这种方法允许开发人员快速启动应用程序所依赖的服务的容器，从而无需手动配置数据库服务器等内容。以这种方式使用 Testcontainers 提供与 Docker Compose 类似的功能，只不过您的容器配置采用 Java 而不是 YAML。

要在开发时使用测试容器，您需要使用“测试”类路径而不是“主”来启动应用程序。这将允许您访问所有声明的测试依赖项，并为您提供一个自然的位置来编写测试配置。

要创建应用程序的测试可启动版本，您应该在`src/test`目录中创建一个“Application”类。例如，如果您的主应用程序位于`src/main/java/com/example/MyApplication.java`，您应该创建`src/test/java/com/example/TestMyApplication.java`

`TestMyApplication`类可以使用`SpringApplication.from(…)`方法来启动真正的应用程序：

```
public class TestMyApplication {
​
    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).run(args);
    }
​
}
```

您还需要定义`Container`要与应用程序一起启动的实例。为此，您需要确保`spring-boot-testcontainers`模块已作为`test`依赖项添加。完成后，您可以创建一个`@TestConfiguration`类来声明要启动的容器的`@Bean`方法。

您还可以使用 `@ServiceConnection`注解您的`@Bean`方法来创建`ConnectionDetails`bean。有关支持的技术的详细信息，请参阅[服务连接部分。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.service-connections)

典型的测试容器配置如下所示：

```
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
​
    @Bean
    @ServiceConnection
    public Neo4jContainer<?> neo4jContainer() {
        return new Neo4jContainer<>("neo4j:5");
    }
​
}
```

> bean的生命周期`Container`由 Spring Boot 自动管理。容器将自动启动和停止。
>
> 您可以使用该`spring.testcontainers.beans.startup`属性来更改容器的启动方式。默认情况下使用`sequential`启动，但您也可以选择`parallel`如果您希望并行启动多个容器。

定义测试配置后，您可以使用`with(…)`方法将其附加到测试启动器：

```
public class TestMyApplication {
​
    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args);
    }
​
}
```

现在，您可以启动`TestMyApplication`像启动任何常规 Java`main`方法应用程序一样启动您的应用程序及其需要运行的容器。

> 您可以使用 Maven 目标`spring-boot:test-run`或 Gradle 任务`bootTestRun`从命令行执行此操作。

**在开发时贡献动态属性**

如果您想在开发时从您的`Container` `@Bean`方法贡献动态属性，您可以通过注入`DynamicPropertyRegistry`. 这与您可以在测试中使用的[`@DynamicPropertySource`注释](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.dynamic-properties)的工作方式类似。它允许您添加容器启动后可用的属性。

典型的配置如下所示：

```
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
​
    @Bean
    public MongoDBContainer mongoDbContainer(DynamicPropertyRegistry properties) {
        MongoDBContainer container = new MongoDBContainer("mongo:5.0");
        properties.add("spring.data.mongodb.host", container::getHost);
        properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
        return container;
    }
​
}
```

> 建议尽可能使用 `@ServiceConnection`，但是，对于尚不支持`@ServiceConnection`的技术，动态属性可能是一个有用的后备方案。

**导入测试容器声明类**

使用测试容器时的常见模式是将`Container`实例声明为静态字段。通常这些字段直接在测试类上定义。它们也可以在父类或测试实现的接口上声明。

例如，以下`MyContainers`接口声明`mongo`和`neo4j`容器：

```
public interface MyContainers {
​
    @Container
    @ServiceConnection
    MongoDBContainer mongoContainer = new MongoDBContainer("mongo:5.0");
​
    @Container
    @ServiceConnection
    Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5");
​
}
```

如果您已经以这种方式定义了容器，或者您只是喜欢这种样式，则可以导入这些声明类，而不是将容器定义为`@Bean`方法。为此，请将`@ImportTestcontainers`注释添加到您的测试配置类中：

```
@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers.class)
public class MyContainersConfiguration {
​
}
```

> 如果您不打算使用[服务连接功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.service-connections)但想使用[`@DynamicPropertySource`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.dynamic-properties)，请从`Container`字段中删除`@ServiceConnection`注释。您还可以将带`@DynamicPropertySource`注释的方法添加到声明类中。

**在开发时将 DevTools 与测试容器结合使用**

使用 devtools 时，您可以使用`@RestartScope` 来注释 bean 和 bean 方法。当开发工具重新启动应用程序时，不会重新创建此类 bean。这对于 Testcontainer `Container`bean 特别有用，因为尽管应用程序重新启动，它们仍保持其状态。

```
@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {
​
    @Bean
    @RestartScope
    @ServiceConnection
    public MongoDBContainer mongoDbContainer() {
        return new MongoDBContainer("mongo:5.0");
    }
​
}
```

> 如果您正在使用 Gradle 并希望使用此功能，则需要将`spring-boot-devtools`依赖项的配置从更改`developmentOnly`为`testImplementation`。使用默认范围`developmentOnly`，`bootTestRun`任务将不会获取代码中的更改，因为开发工具未处于活动状态。

<br>


# 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 的核心功能将因核心启动器的存在而受到尊重。


# 7.13. Kotlin 支持

[Kotlin](https://kotlinlang.org/)是一种针对 JVM（和其他平台）的静态类型语言，它允许编写简洁优雅的代码，同时提供与用 Java 编写的现有库的[互操作性。](https://kotlinlang.org/docs/reference/java-interop.html)

Spring Boot 通过利用其他 Spring 项目（如 Spring Framework、Spring Data 和 Reactor）中的支持来提供 Kotlin 支持。有关更多信息，请参阅[Spring Framework Kotlin 支持文档](https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/languages.html#kotlin)。

开始使用 Spring Boot 和 Kotlin 的最简单方法是遵循[这个综合教程](https://spring.io/guides/tutorials/spring-boot-kotlin/)。您可以使用[start.spring.io](https://start.spring.io/#!language=kotlin)创建新的 Kotlin 项目。如果您需要支持，请随时加入[Kotlin Slack](https://slack.kotlinlang.org/)的#spring 频道，或者在[Stack Overflow](https://stackoverflow.com/questions/tagged/spring+kotlin)上使用`spring`和`kotlin`标签提出问题。

#### 7.13.1.要求

Spring Boot 至少需要 Kotlin 1.3.x，并通过依赖管理来管理合适的 Kotlin 版本。要使用 Kotlin，`org.jetbrains.kotlin:kotlin-stdlib`且`org.jetbrains.kotlin:kotlin-reflect`必须存在于类路径中。也可以使用`kotlin-stdlib`的变体`kotlin-stdlib-jdk7`和`kotlin-stdlib-jdk8`。

由于[Kotlin 类默认为 final](https://discuss.kotlinlang.org/t/classes-final-by-default/166)，您可能需要配置[kotlin-spring](https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support)插件以自动打开带有 Spring 注解的类，以便它们可以被代理。

在 Kotlin中序列化/反序列化 JSON 数据需要[Jackson 的 Kotlin 模块。](https://github.com/FasterXML/jackson-module-kotlin)在类路径中找到它时会自动注册。如果 Jackson 和 Kotlin 存在但 Jackson Kotlin 模块不存在，则会记录一条警告消息。

如果在[start.spring.io](https://start.spring.io/#!language=kotlin) 上引导 Kotlin 项目，则默认提供这些依赖项和插件。

#### 7.13.2. 空值安全

Kotlin 的主要功能之一是[null-safety](https://kotlinlang.org/docs/reference/null-safety.html)。它在编译时处理`null`值，而不是将问题推迟到运行时并遇到`NullPointerException`. 这有助于消除常见的错误来源，而无需使用`Optional`. Kotlin 还允许使用具有可为空值的函数构造，如本[Kotlin 中空安全综合指南中](https://www.baeldung.com/kotlin-null-safety)所述。

尽管 Java 不允许在其类型系统中表达 null 安全性，但 Spring Framework、Spring Data 和 Reactor 现在通过工具友好的注解为其 API 提供 null 安全性。默认情况下，Kotlin 中使用的 Java API 的类型被识别为放宽空检查的 [平台类型。](https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types)[Kotlin 对 JSR 305 注解](https://kotlinlang.org/docs/reference/java-interop.html#jsr-305-support)和可空性注解的支持为 Kotlin 中的相关 Spring API 提供了空值安全性。

可以通过添加`-Xjsr305`带有以下选项的编译器标志来配置 JSR 305 检查：`-Xjsr305={strict|warn|ignore}`. 默认行为与`-Xjsr305=warn` 相同。`strict`值需要在从 Spring API 推断的 Kotlin 类型中考虑空安全性，但应在知道 Spring API 可空性声明甚至在次要版本之间演变并且将来可能会添加更多检查的情况下使用该值）。

尚不支持泛型类型参数、可变参数和数组元素可空性。有关最新信息，请参阅[SPR-15942 。](https://jira.spring.io/browse/SPR-15942)另请注意，Spring Boot 自己的 API[尚未注解](https://github.com/spring-projects/spring-boot/issues/10712)。

#### 7.13.3. Kotlin API

**7.13.3.1. 运行应用程序**

Spring Boot 提供了一种惯用的方式来运行`runApplication<MyApplication>(*args)`应用程序，如以下示例所示：

```
@SpringBootApplication
class MyApplication
​
fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}
```

这是`SpringApplication.run(MyApplication::class.java, *args)`的内部替代. 它还允许自定义应用程序，如以下示例所示：

```
runApplication<MyApplication>(*args) {
    setBannerMode(OFF)
}
```

**7.13.3.2. 扩展**

Kotlin[扩展](https://kotlinlang.org/docs/reference/extensions.html)提供了使用附加功能扩展现有类的能力。Spring Boot Kotlin API 利用这些扩展为现有 API 添加新的 Kotlin 特定便利。

提供了类似于 Spring Framework `RestOperations`为Spring Framework 提供的`TestRestTemplate`扩展。除其他外，这些扩展使利用 Kotlin 实体化类型参数成为可能。

#### 7.13.4. 依赖管理

为了避免在类路径上混合不同版本的 Kotlin 依赖项，Spring Boot 导入了 Kotlin BOM。

使用 Maven，可以通过设置`kotlin-maven-plugin`提供的`kotlin.version`属性来自定义 Kotlin . 通过 Gradle，Spring Boot 插件会自动与Kotlin 插件的`kotlin.version`版本对齐。

Spring Boot 还通过导入 Kotlin Coroutines BOM 来管理 Coroutines 依赖项的版本。可以通过设置`kotlin-coroutines.version`属性来自定义版本。 [如果一个 Kotlin 项目在start.spring.io](https://start.spring.io/#!language=kotlin)上至少有一个反应性依赖项，则默认情况下会提供`org.jetbrains.kotlinx:kotlinx-coroutines-reactor`依赖项。

#### 7.13.5. @ConfigurationProperties

当`@ConfigurationProperties`与[`@ConstructorBinding`](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.typesafe-configuration-properties.constructor-binding)具有不可变属性的支持类结合`val`使用时，如下例所示：

```
@ConstructorBinding
@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
        val name: String,
        val description: String,
        val myService: MyService) {
​
    data class MyService(
            val apiToken: String,
            val uri: URI
    )
}
```

要使用注解处理器 生成[您自己的元数据，](https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor)[`kapt`应配置](https://kotlinlang.org/docs/reference/kapt.html)`spring-boot-configuration-processor`依赖项。请注意，由于 kapt 提供的模型的限制，某些功能（例如检测默认值或不推荐使用的项目）无法正常工作。

#### 7.13.6.测试

虽然可以使用 JUnit 4 来测试 Kotlin 代码，但 JUnit 5 默认提供并推荐使用。JUnit 5 允许一个测试类被实例化一次，并被重用于该类的所有测试。这使得在非静态方法上使用`@BeforeAll`和`@AfterAll`注解成为可能，这非常适合 Kotlin。

要模拟 Kotlin 类，建议使用[MockK](https://mockk.io/)。如果您需要`Mockk`等效的 Mockito 特定[`@MockBean`和`@SpyBean`注解](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.mocking-beans)，您可以使用提供类似`@MockkBean`和`@SpykBean`注解的[SpringMockK](https://github.com/Ninja-Squad/springmockk)。

#### 7.13.7.资源

**7.13.7.1. 进一步阅读**

* [Kotlin 语言参考](https://kotlinlang.org/docs/reference/)
* [Kotlin Slack](https://kotlinlang.slack.com/)（带有专用的#spring 频道）
* [Stackoverflow`spring`和`kotlin`标签](https://stackoverflow.com/questions/tagged/spring+kotlin)
* [在浏览器中试用 Kotlin](https://try.kotlinlang.org/)
* [博客](https://blog.jetbrains.com/kotlin/)
* [很棒的](https://kotlin.link/)
* [教程：使用 Spring Boot 和 Kotlin 构建 Web 应用程序](https://spring.io/guides/tutorials/spring-boot-kotlin/)
* [使用 Kotlin 开发 Spring Boot 应用程序](https://spring.io/blog/2016/02/15/developing-spring-boot-applications-with-kotlin)
* [带有 Kotlin、Spring Boot 和 PostgreSQL 的地理空间信使](https://spring.io/blog/2016/03/20/a-geospatial-messenger-with-kotlin-spring-boot-and-postgresql)
* [在 Spring Framework 5.0 中引入 Kotlin 支持](https://spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0)
* [Spring Framework 5 Kotlin API，函数式方式](https://spring.io/blog/2017/08/01/spring-framework-5-kotlin-apis-the-functional-way)

**7.13.7.2. 例子**

* [spring-boot-kotlin-demo](https://github.com/sdeleuze/spring-boot-kotlin-demo)：常规 Spring Boot + Spring Data JPA 项目
* [mixit](https://github.com/mixitconf/mixit)：Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB
* [spring-kotlin-fullstack](https://github.com/sdeleuze/spring-kotlin-fullstack)：WebFlux Kotlin fullstack 示例，前端使用 Kotlin2js 而不是 JavaScript 或 TypeScript
* [spring-petclinic-kotlin](https://github.com/spring-petclinic/spring-petclinic-kotlin)：Spring PetClinic 示例应用程序的 Kotlin 版本
* [spring-kotlin-deepdive](https://github.com/sdeleuze/spring-kotlin-deepdive)：从 Boot 1.0 + Java 到 Boot 2.0 + Kotlin 的逐步迁移
* [spring-boot-coroutines-demo](https://github.com/sdeleuze/spring-boot-coroutines-demo) : 协程示例项目

###


# 7.14 SSL

Spring Boot 提供了配置 SSL 信任材料的能力，该材料可应用于多种类型的连接以支持安全通信。带有 `spring.ssl.bundle` 前缀的配置属性可用于指定信任材料和关联信息的命名集。

**7.14.1. 使用 Java KeyStore 文件配置 SSL**

带有`spring.ssl.bundle.jks`前缀的配置属性可用于配置使用 Java`keytool`实用程序创建并以 JKS 或 PKCS12 格式存储在 Java KeyStore 文件中的信任材料包。每个捆绑包都有一个用户提供的名称，可用于引用该捆绑包。

当用于保护嵌入式 Web 服务器时，`keystore`通常配置一个包含证书和私钥的 Java KeyStore，如下例所示：

```
spring.ssl.bundle.jks.mybundle.key.alias=application
spring.ssl.bundle.jks.mybundle.keystore.location=classpath:application.p12
spring.ssl.bundle.jks.mybundle.keystore.password=secret
spring.ssl.bundle.jks.mybundle.keystore.type=PKCS12
```

当用于保护客户端连接时，`truststore`通常使用包含服务器证书的 Java KeyStore 进行配置，如下例所示：

```
spring.ssl.bundle.jks.mybundle.truststore.location=classpath:server.p12
spring.ssl.bundle.jks.mybundle.truststore.password=secret
```

有关完整支持的属性集，请参阅[JksSslBundleProperties 。](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/JksSslBundleProperties.java)

**7.14.2. 使用 PEM 编码的证书配置 SSL**

带有`spring.ssl.bundle.pem`前缀的配置属性可用于以 PEM 编码文本的形式配置信任材料包。每个捆绑包都有一个用户提供的名称，可用于引用该捆绑包。

当用于保护嵌入式 Web 服务器时，`keystore`通常会配置证书和私钥，如下例所示：

```
spring.ssl.bundle.pem.mybundle.keystore.certificate=classpath:application.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=classpath:application.key
```

当用于保护客户端连接时，`truststore`通常使用服务器证书进行配置，如下例所示：

```
spring.ssl.bundle.pem.mybundle.truststore.certificate=classpath:server.crt
```

PEM 内容可直接用于`certificate`和`private-key`属性。如果属性值包含`BEGIN`和`END`标记，那么它们将被视为 PEM 内容而不是资源位置。以下示例显示如何定义信任库证书：

```
spring.ssl.bundle.pem.mybundle.truststore.certificate=\
-----BEGIN CERTIFICATE-----\n\
MIID1zCCAr+gAwIBAgIUNM5QQv8IzVQsgSmmdPQNaqyzWs4wDQYJKoZIhvcNAQEL\n\
BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI\n\
...\n\
V0IJjcmYjEZbTvpjFKznvaFiOUv+8L7jHQ1/Yf+9c3C8gSjdUfv88m17pqYXd+Ds\n\
HEmfmNNjht130UyjNCITmLVXyy5p35vWmdf95U3uEbJSnNVtXH8qRmN9oK9mUpDb\n\
ngX6JBJI7fw7tXoqWSLHNiBODM88fUlQSho8\n\
-----END CERTIFICATE-----\n
```

有关完整支持的属性集，请参阅[PemSslBundleProperties 。](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PemSslBundleProperties.java)

**7.14.3. 应用 SSL 捆绑包**

使用属性进行配置后，可以在配置属性中通过名称引用 SSL 捆绑包，以用于 Spring Boot 自动配置的各种类型的连接。[有关更多信息，请参阅有关嵌入式 Web 服务器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.configure-ssl)、[数据技术](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data)和[REST 客户](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.rest-client)端的部分。

**7.14.4. 使用 SSL 捆绑包**

Spring Boot 自动配置一个`SslBundles` 类型的bean ，该 bean 提供对使用`spring.ssl.bundle`属性配置的每个命名包的访问。

`SslBundle`可以从自动配置的`SslBundles`bean 中检索并用于创建用于在客户端库中配置 SSL 连接的对象。`SslBundle`提供了获取这些 SSL 对象的分层方法：

* `getStores()`提供对密钥存储和信任存储`java.security.KeyStore`实例以及任何所需的密钥存储密码的访问。
* `getManagers()`提供对`java.net.ssl.KeyManagerFactory`和`java.net.ssl.TrustManagerFactory`实例以及它们创建的`java.net.ssl.KeyManager`和`java.net.ssl.TrustManager`数组的访问。
* `createSslContext()`提供了一种获取新`java.net.ssl.SSLContext`实例的便捷方法。

此外，`SslBundle`还提供了有关正在使用的密钥、要使用的协议以及应应用于 SSL 引擎的任何选项的详细信息。

以下示例展示了检索 `SslBundle`并使用它创建 `SSLContext`：

```
@Component
public class MyComponent {
​
    public MyComponent(SslBundles sslBundles) {
        SslBundle sslBundle = sslBundles.getBundle("mybundle");
        SSLContext sslContext = sslBundle.createSslContext();
        // do something with the created sslContext
    }
​
}
```

**7.14.5. 重新加载 SSL 捆绑包**

当密钥材料发生变化时，可以重新加载 SSL 捆绑包。使用该捆绑包的组件必须与可重新加载的 SSL 捆绑包兼容。目前以下组件兼容：

* Tomcat 网络服务器
* Netty网络服务器

要启用重新加载，您需要通过配置属性选择加入，如本示例所示：

```
spring.ssl.bundle.pem.mybundle.reload-on-update=true
spring.ssl.bundle.pem.mybundle.keystore.certificate=file:/some/directory/application.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=file:/some/directory/application.key
```

然后，文件监视程序会监视文件，如果文件发生更改，SSL 捆绑包将被重新加载。这反过来会触发使用组件中的重新加载，例如 Tomcat 在启用 SSL 的连接器中轮换证书。

您可以使用`spring.ssl.bundle.watch.file.quiet-period`属性配置文件监视程序的安静期（以确保不再发生更改）。


# 7.15.接下来要读什么

如果您想了解有关本节中讨论的任何类的更多信息，请参阅[Spring Boot API 文档](https://docs.spring.io/spring-boot/docs/2.7.3/api/)，或者您可以直接浏览[源代码](https://github.com/spring-projects/spring-boot/tree/v2.7.3)。如果您有具体问题，请参阅[操作方法](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto)部


# 8. 网络

Spring Boot 非常适合 Web 应用程序开发。您可以使用嵌入式 Tomcat、Jetty、Undertow 或 Netty 创建独立的 HTTP 服务器。大多数 Web 应用程序都使用该`spring-boot-starter-web`模块来快速启动和运行。您还可以选择使用该`spring-boot-starter-webflux`模块构建反应式 Web 应用程序。

如果您还没有开发过 Spring Boot Web 应用程序，您可以按照“Hello World!”进行操作。[*入门*](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started.first-application)部分中的示例。

####


# 8.1. Servlet Web 应用程序

如果您想构建基于 servlet 的 Web 应用程序，您可以利用 Spring Boot 对 Spring MVC 或 Jersey 的自动配置。


# 8.1.1. “Spring Web MVC 框架”

Spring [Web MVC 框架](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc.html)（通常称为“Spring MVC”）是一个丰富的“模型视图控制器”Web 框架。Spring MVC 允许您创建指定的`@Controller`或`@RestController`Bean 来处理传入的 HTTP 请求。控制器中的方法通过使用 `@RequestMapping`注释映射到 HTTP。

以下代码显示了`@RestController`提供 JSON 数据的典型代码：

```
@RestController
@RequestMapping("/users")
public class MyRestController {
​
    private final UserRepository userRepository;
​
    private final CustomerRepository customerRepository;
​
    public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
        this.userRepository = userRepository;
        this.customerRepository = customerRepository;
    }
​
    @GetMapping("/{userId}")
    public User getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId).get();
    }
​
    @GetMapping("/{userId}/customers")
    public List<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
    }
​
    @DeleteMapping("/{userId}")
    public void deleteUser(@PathVariable Long userId) {
        this.userRepository.deleteById(userId);
    }
​
}
```

“WebMvc.fn”，功能变体，将路由配置与请求的实际处理分开，如以下示例所示：

```
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
​
    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
​
    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }
​
}
```

```
@Component
public class MyUserHandler {
​
    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
​
    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
​
    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
​
}
```

Spring MVC 是核心 Spring 框架的一部分，详细信息可在 [参考文档](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc.html)中找到。[spring.io/guides](https://spring.io/guides)上还提供了一些涵盖 Spring MVC 的指南。

> 您可以定义任意数量的`RouterFunction`bean，以模块化路由器的定义。如果您需要应用优先级，可以对bean类排序。

**Spring MVC 自动配置**

Spring Boot 为 Spring MVC 提供自动配置，适用于大多数应用程序。它取代了需要`@EnableWebMvc`，并且两者不能一起使用。除了 Spring MVC 的默认设置之外，自动配置还提供以下功能：

* 包含`ContentNegotiatingViewResolver`和`BeanNameViewResolver`Bean类。
* 支持提供静态资源，包括对 WebJars 的支持（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.static-content)介绍）。
* 自动注册`Converter`、`GenericConverter`和`Formatter`beans。
* 支持`HttpMessageConverters`（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.message-converters)介绍）。
* 自动注册`MessageCodesResolver`（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.message-codes)介绍）。
* 静态`index.html`支持。
* 自动使用`ConfigurableWebBindingInitializer`bean（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.binding-initializer)介绍）。

如果您想保留这些 Spring Boot MVC 自定义并进行更多 MVC 自定义（拦截器、格式化程序、视图控制器和其他功能），您可以添加自己的`WebMvcConfigurer` 类型的 `@Configuration` 类，但不使用 `@EnableWebMvc`。

如果您想要提供`RequestMappingHandlerMapping`、`RequestMappingHandlerAdapter`、 或 `ExceptionHandlerExceptionResolver`的自定义实例，并且仍然保留 Spring Boot MVC 自定义，则可以声明`WebMvcRegistrations`类型的 bean并使用它来提供这些组件的自定义实例。自定义实例将由 Spring MVC 进行进一步的初始化和配置。要参与并在需要时覆盖后续处理，应使用`WebMvcConfigurer`。

如果您不想使用自动配置并希望完全控制 Spring MVC，请添加您自己的带有 `@EnableWebMvc`注释的 `@Configuration`。或者，添加您自己的带有 `@Configuration` 注释的 `DelegatingWebMvcConfiguration`，如`@EnableWebMvc` 的 Javadoc 中所述。

**Spring MVC 转换服务**

Spring MVC 使用一种与用于转换您`application.properties`或`application.yaml`文件中的值的方法不同的`ConversionService`方法。这意味着`Period`,`Duration`和`DataSize`转换器不可用，并且`@DurationUnit`和`@DataSizeUnit`注释将被忽略。

如果你想定制Spring MVC使用的`ConversionService`，你可以提供一个带有`addFormatters`方法的`WebMvcConfigurer` bean 。通过此方法，您可以注册任何您喜欢的转换器，也可以委托给`ApplicationConversionService`.

还可以使用`spring.mvc.format.*`配置属性来自定义转换。如果未配置，则使用以下默认值：

| 属性                            | `DateTimeFormatter`                      |
| ----------------------------- | ---------------------------------------- |
| `spring.mvc.format.date`      | `ofLocalizedDate(FormatStyle.SHORT)`     |
| `spring.mvc.format.time`      | `ofLocalizedTime(FormatStyle.SHORT)`     |
| `spring.mvc.format.date-time` | `ofLocalizedDateTime(FormatStyle.SHORT)` |

**Http消息转换器**

Spring MVC 使用`HttpMessageConverter`接口来转换 HTTP 请求和响应。合理的默认值是开箱即用的。例如，对象可以自动转换为 JSON（通过使用 Jackson 库）或 XML（通过使用 Jackson XML 扩展（如果可用），或者通过使用 JAXB（如果 Jackson XML 扩展不可用））。默认情况下，字符串以`UTF-8`编码.

如果需要添加或自定义转换器，可以使用 Spring Boot 的`HttpMessageConverters`类，如下清单所示：

```
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
​
    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
        HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
        return new HttpMessageConverters(additional, another);
    }
​
}
```

上下文中存在的任何`HttpMessageConverter`bean 都会添加到转换器列表中。您还可以以相同的方式覆盖默认转换器。

**消息代码解析器**

Spring MVC 有一个生成错误代码的策略，用于从绑定错误中呈现错误消息：`MessageCodesResolver`。如果您设置`spring.mvc.message-codes-resolver-format`属性为`PREFIX_ERROR_CODE`或`POSTFIX_ERROR_CODE`，Spring Boot 会为您创建一个属性（请参阅[`DefaultMessageCodesResolver.Format`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/validation/DefaultMessageCodesResolver.Format.html)中的枚举）。

**静态内容**

默认情况下，Spring Boot 从类路径中名为 `/static`（或`/public` 或`/resources` 或`/META-INF/resources`）的目录或 `ServletContext` 的根目录提供静态内容，它使用 Spring MVC 中的 `ResourceHttpRequestHandler`，以便您可以通过添加自己的 `WebMvcConfigurer` 并重写 `addResourceHandlers` 方法来修改该行为。

在独立的 Web 应用程序中，容器中的默认 servlet 未启用。可以使用`server.servlet.register-default-servlet`属性来启用它。

默认 servlet 充当后备，如果 Spring 决定不处理它，则从 `ServletContext` 的根提供内容。大多数时候，这种情况不会发生（除非您修改默认的 MVC 配置），因为 Spring 始终可以通过 `DispatcherServlet` 处理请求。

默认情况下，资源映射到`/**`，但您可以使用`spring.mvc.static-path-pattern`属性对其进行调整。例如，将所有资源重新定位`/resources/**`可以实现如下：

```
spring.mvc.static-path-pattern=/resources/**
```

您还可以使用`spring.web.resources.static-locations` 属性自定义静态资源位置（用目录位置列表替换默认值）。根 Servlet 上下文路径`"/"`也会自动添加为位置。

除了前面提到的“标准”静态资源位置之外，还为[Webjars 内容](https://www.webjars.org/)制作了一个特例。默认情况下，如果以 Webjars 格式打包，则任何路径为`/webjars/**` 的资源都将从 jar 文件提供。可以使用属性`spring.mvc.webjars-path-pattern`自定义路径。

> 如果您的应用程序打包为 jar， 请勿使用`src/main/webapp`目录。尽管此目录是通用标准，但它**仅**适用于 war 打包，并且如果您生成 jar，大多数构建工具都会默默地忽略它。

Spring Boot 还支持 Spring MVC 提供的高级资源处理功能，允许使用缓存清除静态资源或对 Webjar 使用版本无关的 URL 等用例。

要对 Webjar 使用与版本无关的 URL，请添加`webjars-locator-core`依赖项。然后声明你的Webjar。然后声明你的Webjar。以 jQuery 为例，添加“`/webjars/jquery/jquery.min.js`”会生成“`/webjars/jquery/x.y.z/jquery.min.js`”，其中 `x.y.z` 是 Webjar 版本。

> 如果您使用 JBoss，则需要声明`webjars-locator-jboss-vfs`依赖项而不是`webjars-locator-core`. 否则，所有 Webjar 都会解析为`404`.

要使用缓存清除，以下配置为所有静态资源配置缓存清除解决方案，有效地在 URL 中添加内容哈希，例如`<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`：

```
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
```

> 由于Thymeleaf 和 FreeMarker 自动配置`ResourceUrlEncodingFilter` ，资源链接在运行时在模板中重写。使用 JSP 时您应该手动声明此过滤器。目前不自动支持其他模板引擎，但可以使用自定义模板宏/帮助程序以及使用[`ResourceUrlProvider`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/web/servlet/resource/ResourceUrlProvider.html).

例如，使用 JavaScript 模块加载器动态加载资源时，不能选择重命名文件。这就是为什么其他策略也受到支持并且可以组合的原因。“固定”策略在 URL 中添加静态版本字符串而不更改文件名，如下例所示：

```
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12
```

通过此配置，位于`"/js/lib/"` 下的 JavaScript 模块使用固定版本控制策略 ( `"/v12/js/lib/mymodule.js"`)，而其他资源仍然使用内容( `<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`)。

请参阅[`WebProperties.Resources`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebProperties.java)参考资料 了解更多支持的选项。

> 此功能已在专门的[博客文章](https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources)和 Spring 框架的[参考文档](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc/mvc-config/static-resources.html)中进行了详细描述。

**欢迎页面**

Spring Boot 支持静态和模板化欢迎页面。它首先在配置的静态内容位置查找`index.html`文件。如果没有找到，它就会寻找`index`模板。如果找到其中一个，它将自动用作应用程序的欢迎页面。

这仅充当应用程序定义的实际索引路由的后备。顺序由`HandlerMapping`bean 的顺序定义，默认情况如下：

<table data-header-hidden><thead><tr><th width="291"></th><th></th></tr></thead><tbody><tr><td>映射bean</td><td>备注</td></tr><tr><td><code>RouterFunctionMapping</code></td><td><code>RouterFunction</code>使用beans声明的端点</td></tr><tr><td><code>RequestMappingHandlerMapping</code></td><td><code>@Controller</code>bean中声明的端点</td></tr><tr><td><code>WelcomePageHandlerMapping</code></td><td>欢迎页面支持</td></tr></tbody></table>

**自定义图标**

与其他静态资源一样，Spring Boot 检查配置的静态内容位置中的 `favicon.ico`。如果存在这样的文件，它将自动用作应用程序的图标。

**路径匹配和内容协商**

Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射（例如，控制器方法上的`@GetMapping`注释）进行匹配，将传入的 HTTP 请求映射到处理程序。

Spring Boot 默认选择禁用后缀模式匹配，这意味着类似的请求`"GET /projects/spring-boot.json"`将不会与`@GetMapping("/projects/spring-boot")`映射匹配。这被认为是[Spring MVC 应用程序的最佳实践](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-suffix-pattern-match)。过去，此功能主要适用于未发送正确“Accept”请求标头的 HTTP 客户端；我们需要确保向客户端发送正确的内容类型。如今，内容协商更加可靠。

还有其他方法可以处理不始终发送正确的“Accept”请求标头的 HTTP 客户端。我们可以使用查询参数来确保将请求`"GET /projects/spring-boot?format=json"`映射到`@GetMapping("/projects/spring-boot")` ，而不是使用后缀匹配：

```
spring.mvc.contentnegotiation.favor-parameter=true
```

或者，如果您更喜欢使用不同的参数名称：

```
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
```

大多数标准媒体类型都是开箱即用的，但您也可以定义新的媒体类型：

```
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
```

从 Spring Framework 5.3 开始，Spring MVC 支持两种将请求路径与控制器匹配的策略。默认情况下，Spring Boot 使用`PathPatternParser`策略。 `PathPatternParser`是一个[优化的实现](https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc)，但与`AntPathMatcher`策略相比有一些限制。`PathPatternParser` [限制某些路径模式变体](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-uri-templates)的使用。它也与使用路径前缀 ( `spring.mvc.servlet.path`)配置的`DispatcherServlet`不兼容。

可以使用`spring.mvc.pathmatch.matching-strategy`配置属性来配置该策略，如以下示例所示：

```
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
```

默认情况下，如果未找到请求的处理程序，Spring MVC 将发送 404 Not Found 错误响应。要改为`NoHandlerFoundException`抛出异常，请将`configprop:spring.mvc.throw-exception-if-no-handler-found` 设置为`true`。请注意，默认情况下，[静态内容的服务](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.static-content)会映射到`/**`并因此为所有请求提供处理程序。对于要抛出的 `NoHandlerFoundException`，您还必须设置`spring.mvc.static-path-pattern`为更具体的值，例如`/resources/**`或 设置`spring.web.resources.add-mappings`为`false`完全禁用静态内容的服务。

**可配置的WebBinding初始化器**

Spring MVC 使用 `WebBindingInitializer`来初始化特定请求的`WebDataBinder`。如果您创建自己的`ConfigurableWebBindingInitializer` `@Bean`，Spring Boot 会自动配置 Spring MVC 来使用它。

**模板引擎**

除了 REST Web 服务之外，您还可以使用 Spring MVC 来提供动态 HTML 内容。Spring MVC 支持多种模板技术，包括 Thymeleaf、FreeMarker 和 JSP。此外，许多其他模板引擎都包含自己的 Spring MVC 集成。

Spring Boot 包括对以下模板引擎的自动配置支持：

* [freemaker](https://freemarker.apache.org/docs/)
* [Groovy](https://docs.groovy-lang.org/docs/next/html/documentation/template-engines.html#_the_markuptemplateengine)
* [thymeleaf](https://www.thymeleaf.org/)
* [mustache](https://mustache.github.io/)

> 如果可能，应避免 JSP。将它们与嵌入式 servlet 容器一起使用时存在一些[已知的限制](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.embedded-container.jsp-limitations)。

当您使用这些模板引擎之一和默认配置时，您的模板会自动从`src/main/resources/templates`载入.

> 根据您运行应用程序的方式，您的 IDE 可能会对类路径进行不同的排序。在 IDE 中从其 main 方法运行应用程序会导致与使用 Maven 或 Gradle 或从其打包的 jar 运行应用程序时不同的顺序。这可能会导致 Spring Boot 无法找到预期的模板。如果遇到此问题，可以在 IDE 中重新排序类路径，将模块的类和资源放在前面。

**错误处理**

默认情况下，Spring Boot 提供了一个以合理方式处理所有错误的`/error`映射，并且它在 servlet 容器中注册为“全局”错误页面。对于机器客户端，它会生成一个 JSON 响应，其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端，有一个“whitelabel”错误视图，它以 HTML 格式呈现相同的数据（要自定义它，请添加解析为`error` 的`View`）。

如果您想自定义默认的错误处理行为，可以设置许多`server.error`属性。请参阅附录的[“服务器属性”部分。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.application-properties.server)

要完全替换默认行为，您可以实现`ErrorController`并注册该类型的 bean 定义，或者添加该类型的 bean`ErrorAttributes`以使用现有机制但替换内容。

> `BasicErrorController`可以用作自定义`ErrorController`的基类。如果您想为新的内容类型添加处理程序（默认情况是专门处理`text/html`并为其他所有内容提供后备），这特别有用。为此，请扩展`BasicErrorController`，添加一个`@RequestMapping`具有`produces`属性的公共方法，然后创建一个新类型的 bean。

从 Spring Framework 6.0 开始，支持[RFC 7807 问题详细信息。](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc/mvc-ann-rest-exceptions.html)Spring MVC 可以使用`application/problem+json`媒体类型生成自定义错误消息，例如：

```
{
  "type": "https://example.org/problems/unknown-project",
  "title": "Unknown project",
  "status": 404,
  "detail": "No project found for id 'spring-unknown'",
  "instance": "/projects/spring-unknown"
}
```

可以通过设置`spring.mvc.problemdetails.enabled`为 `true`来启用此支持。

您还可以定义一个带`@ControllerAdvice`注释的类，以自定义要针对特定控制器和/或异常类型返回的 JSON 文档，如以下示例所示：

```
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
​
    @ResponseBody
    @ExceptionHandler(MyException.class)
    public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
    }
​
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = HttpStatus.resolve(code);
        return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
    }
​
}
```

在前面的示例中，如果`MyException`是由与 位于同一包中定义的`SomeController`控制器抛出的，则使用 POJO 的 JSON 表示形式`MyErrorBody`而不是`ErrorAttributes`表示形式。

[在某些情况下，度量基础设施](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.metrics.supported.spring-mvc)不会记录在控制器级别处理的错误。应用程序可以通过将已处理的异常设置为请求属性来确保此类异常与请求指标一起记录：

```
@Controller
public class MyController {
​
    @ExceptionHandler(CustomException.class)
    String handleCustomException(HttpServletRequest request, CustomException ex) {
        request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
        return "errorView";
    }
​
}
```

**自定义错误页面**

如果要显示给定状态代码的自定义 HTML 错误页面，可以将文件添加到目录`/error`。错误页面可以是静态 HTML（即添加在任何静态资源目录下），也可以使用模板构建。文件名应该是确切的状态代码或系列掩码。

例如，要映射`404`到静态 HTML 文件，您的目录结构将如下所示：

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>
```

要使用 FreeMarker 模板映射所有`5xx`错误，您的目录结构将如下所示：

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftlh
             +- <other templates>
```

对于更复杂的映射，您还可以添加实现`ErrorViewResolver`接口的 bean，如以下示例所示：

```
public class MyErrorViewResolver implements ErrorViewResolver {
​
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        // Use the request or status to optionally return a ModelAndView
        if (status == HttpStatus.INSUFFICIENT_STORAGE) {
            // We could add custom model values here
            new ModelAndView("myview");
        }
        return null;
    }
​
}
```

您还可以使用常规 Spring MVC 功能，例如[`@ExceptionHandler`方法](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc/mvc-servlet/exceptionhandlers.html)和[`@ControllerAdvice`](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc/mvc-controller/ann-advice.html). `ErrorController`会拾取任何未处理的异常。

**在 Spring MVC 之外映射错误页面**

对于不使用Spring MVC的应用程序，可以使用`ErrorPageRegistrar`接口直接注册`ErrorPages`。这种抽象直接与底层嵌入式 servlet 容器一起工作，即使您没有 Spring MVC `DispatcherServlet`也可以工作。

```
@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {
​
    @Bean
    public ErrorPageRegistrar errorPageRegistrar() {
        return this::registerErrorPages;
    }
​
    private void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
    }
​
}
```

如果您注册 `ErrorPage` 的路径最终由 `Filter` 处理（这在某些非 Spring Web 框架中很常见，例如 Jersey 和 Wicket），则必须将 `Filter`显式注册为`ERROR`调度程序，如以下示例所示：

```
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
​
    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
        // ...
        registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
        return registration;
    }
​
}
```

请注意，默认值`FilterRegistrationBean`不包括`ERROR`调度程序类型。

**WAR 部署中的错误处理**

当部署到 servlet 容器时，Spring Boot 使用其错误页面过滤器将带有错误状态的请求转发到适当的错误页面。这是必要的，因为 servlet 规范没有提供用于注册错误页面的 API。根据您将 war 文件部署到的容器以及应用程序使用的技术，可能需要一些额外的配置。

如果响应尚未提交，错误页面过滤器只能将请求转发到正确的错误页面。默认情况下，WebSphere Application Server 8.0 及更高版本在成功完成 servlet 的服务方法后提交响应。您应该通过设置`com.ibm.ws.webcontainer.invokeFlushAfterService`为`false` 来禁用此行为。

**CORS 支持**

[跨域资源共享](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)(CORS) 是[大多数浏览器](https://caniuse.com/#feat=cors)实现的[W3C 规范](https://www.w3.org/TR/cors/)，它允许您以灵活的方式指定授权哪种跨域请求，而不是使用一些不太安全且功能不太强大的方法，例如 IFRAME 或 JSONP。

从版本 4.2 开始，Spring MVC[支持 CORS](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc-cors.html)。在 Spring Boot 应用程序中使用带[`@CrossOrigin`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/web/bind/annotation/CrossOrigin.html)注释的[控制器方法 CORS 配置](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc-cors.html#mvc-cors-controller)不需要任何特定配置。 可以通过自定义`addCorsMappings(CorsRegistry)`方法注册`WebMvcConfigurer`bean来定义[全局CORS配置](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc-cors.html#mvc-cors-global)，如下例所示：

```
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
​
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
​
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**");
            }
​
        };
    }
​
}
```


# 8.1.2. JAX-RS 和Jersey

如果您更喜欢 REST 端点的 JAX-RS 编程模型，则可以使用可用的实现之一而不是 Spring MVC。 [Jersey](https://jersey.github.io/)和[Apache CXF](https://cxf.apache.org/)开箱即用，运行良好。CXF 要求您在应用程序上下文中将其注册为`Servlet`或`Filter`作为 `@Bean` 。Jersey 有一些原生 Spring 支持，因此我们还在 Spring Boot 中为其提供自动配置支持以及启动器。

要开始使用 Jersey，请包含 `spring-boot-starter-jersey` 作为依赖项，然后您需要一个`ResourceConfig`类型的 `@Bean`，在其中注册所有端点，如以下示例所示：

```
@Component
public class MyJerseyConfig extends ResourceConfig {
​
    public MyJerseyConfig() {
        register(MyEndpoint.class);
    }
​
}
```

> Jersey 对扫描可执行档案的支持相当有限。例如，它无法扫描在[完全可执行的 jar 文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#deployment.installing)中或`WEB-INF/classes`运行可执行的 war 文件时找到的包中的端点。为了避免这种限制，不应使用`packages`方法，而应使用该`register`方法单独注册端点，如前面的示例所示。

对于更高级的自定义，您还可以注册任意数量的`ResourceConfigCustomizer`实现.

所有注册的端点都应该是带有HTTP资源注释（`@GET`和其他）的`@Components`，如下例所示：

```
@Component
@Path("/hello")
public class MyEndpoint {
​
    @GET
    public String message() {
        return "Hello";
    }
​
}
```

由于`Endpoint`是一个 Spring `@Component`，它的生命周期由 Spring 管理，您可以使用`@Autowired`注解注入依赖项，并使用`@Value`注解注入外部配置。默认情况下，Jersey servlet 已注册并映射到`/*`. 您可以通过添加`@ApplicationPath`到您的`ResourceConfig`.

默认情况下，Jersey 在名为 `jerseyServletRegistration` 的 `ServletRegistrationBean` 类型的 `@Bean`中设置为 servlet。默认情况下，Servlet 是延迟初始化的，但您可以通过设置 `spring.jersey.servlet.load-on-startup`来自定义该行为。您可以通过创建您自己的同名 bean 来禁用或覆盖该 bean。您还可以通过设置 `spring.jersey.type=filter`来使用过滤器而不是 servlet（在这种情况下，要替换或覆盖的 @Bean 是 `jerseyFilterRegistration`）。该过滤器有一个`@Order`，您可以使用`spring.jersey.filter.order` 设置它。当使用 Jersey 作为过滤器时，必须存在一个 servlet 来处理任何未被 Jersey 拦截的请求。如果您的应用程序不包含此类 servlet，您可能需要通过将`server.servlet.register-default-servlet`设置为 `true`来启用默认 servlet。 servlet 和过滤器注册都可以通过使用 `spring.jersey.init.*` 指定属性映射来指定初始化参数。


# 8.1.3. 嵌入式 Servlet 容器支持

[对于 servlet 应用程序，Spring Boot 包括对嵌入式Tomcat](https://tomcat.apache.org/)、[Jetty](https://www.eclipse.org/jetty/)和[Undertow](https://github.com/undertow-io/undertow)服务器的支持。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下，嵌入式服务器侦听端口 `8080`上的 HTTP 请求。

**Servlet、过滤器和侦听器**

使用嵌入式 servlet 容器时，您可以通过使用 Spring beans 或扫描 servlet 组件来注册 servlet、过滤器和 servlet 规范中的所有侦听器（例如`HttpSessionListener`）。

**将 Servlet、过滤器和侦听器注册为 Spring Bean**

任何属于Spring bean 的`Servlet`、`Filter`、 或 servlet`*Listener`实例都注册到嵌入式容器中。如果您想在配置期间引用`application.properties`中的值，这会特别方便。

默认情况下，如果上下文仅包含单个 Servlet，则它将映射到`/`. 对于多个 servlet bean，bean 名称用作路径前缀。过滤器映射到`/*`.

如果基于约定的映射不够灵活，您可以使用`ServletRegistrationBean`、`FilterRegistrationBean`和`ServletListenerRegistrationBean`类进行完全控制。

通常，让filter bean保持无序状态是安全的。如果需要特定的顺序，您应该使用`@Order`注释`Filter`或使其实现`Ordered`。您不能通过使用`@Order` 注释其 bean 方法来配置`Filter` 的顺序。如果您无法为`Filter`添加`@Order`或实现的类`Ordered`，则必须为`Filter`定义`FilterRegistrationBean`并使用 `setOrder(int)`方法设置注册 bean 的顺序。避免配置在 `Ordered.HIGHEST_PRECEDENCE` 处读取请求正文的过滤器，因为它可能会违反应用程序的字符编码配置。如果 servlet 过滤器包装请求，则应配置小于或等于 `OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER` 的顺序。

> 要查看应用程序中 每个 `Filter`项的顺序，请为`web` [日志记录组](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.logging.log-groups)( `logging.level.web=debug`) 启用调试级别日志记录。已注册过滤器的详细信息（包括它们的顺序和 URL 模式）将在启动时记录下来。
>
> 注册`Filter` bean 时要小心，因为它们是在应用程序生命周期的早期初始化的。如果您需要注册`Filter`与其他 bean 交互，请考虑使用 [`DelegatingFilterProxyRegistrationBean`](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/web/servlet/DelegatingFilterProxyRegistrationBean.html)来代替。

**Servlet 上下文初始化**

嵌入式servlet容器不直接执行`jakarta.servlet.ServletContainerInitializer`接口或Spring的`org.springframework.web.WebApplicationInitializer`接口。这是一个有意的设计决策，旨在降低设计用于在战争中运行的第三方库可能破坏 Spring Boot 应用程序的风险。

如果需要在 Spring Boot 应用程序中执行 servlet 上下文初始化，则应该注册一个实现`org.springframework.boot.web.servlet.ServletContextInitializer`接口的 bean。单一`onStartup`方法提供对 `ServletContext`的访问，并且如果需要，可以轻松地用作现有`WebApplicationInitializer`的适配器.

**扫描 Servlet、过滤器和侦听器**

使用嵌入式容器时，可以使用 `@ServletComponentScan`启用自动注册用`@WebServlet`、`@WebFilter`、 和`@WebListener`注释的类。

> `@ServletComponentScan`在独立容器中没有效果，由于它使用容器的内置发现机制。

**ServletWebServerApplicationContext**

在底层，Spring Boot 使用不同类型的`ApplicationContext`提供嵌入式 servlet 容器支持。`ServletWebServerApplicationContext`是一种特殊类型`WebApplicationContext`，它通过搜索单个`ServletWebServerFactory`bean 来引导自身。通常`TomcatServletWebServerFactory`、`JettyServletWebServerFactory`、 或`UndertowServletWebServerFactory`都会被自动配置。

> 您通常不需要了解这些实现类。大多数应用程序都是自动配置的，并且会代表您创建适当的`ApplicationContext`和`ServletWebServerFactory`。

在嵌入式容器设置中，`ServletContext`被设置为服务器启动的一部分，该启动在应用程序上下文初始化期间发生。因此，`ApplicationContext`不能使用 `ServletContext`初始化中的 beans 。解决这个问题的一种方法是`ApplicationContext`作为 bean 的依赖项注入并`ServletContext`仅在需要时访问。另一种方法是在服务器启动后使用回调。这可以使用`ApplicationListener`监听`ApplicationStartedEvent`来完成，如下所示：

```
public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {
​
    private ServletContext servletContext;
​
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
    }
​
}
```

**自定义嵌入式 Servlet 容器**

常见的 servlet 容器设置可以使用 Spring`Environment`属性进行配置。通常，您可以在您的`application.properties`或`application.yaml`文件中定义属性。

常见的服务器设置包括：

* 网络设置：传入 HTTP 请求的侦听端口 ( `server.port`)、要绑定的接口地址 ( `server.address`) 等。
* 会话设置：会话是否持久 ( `server.servlet.session.persistent`)、会话超时 ( `server.servlet.session.timeout`)、会话数据位置 ( `server.servlet.session.store-dir`) 和会话 cookie 配置 ( `server.servlet.session.cookie.*`)。
* 错误管理：错误页面的位置（`server.error.path`）等。
* [SSL协议](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.configure-ssl)
* [HTTP 压缩](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.enable-response-compression)

Spring Boot 尝试尽可能多地公开通用设置，但这并不总是可行。对于这些情况，专用命名空间提供特定于服务器的自定义（请参阅`server.tomcat`和`server.undertow`）。例如，可以使用嵌入式 servlet 容器的特定功能来配置[访问日志。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.configure-access-logs)

> 请参阅[`ServerProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java)课程以获取完整列表。

**同站点 Cookie**

Web 浏览器可以使用cookie属性`SameSite`来控制在跨站点请求中是否提交 cookie 以及如何提交。该属性与现代 Web 浏览器特别相关，现代 Web 浏览器已开始更改该属性丢失时使用的默认值。

如果您想更改会话 cookie 的`SameSite`属性，可以使用`server.servlet.session.cookie.same-site`属性。自动配置的 Tomcat、Jetty 和 Undertow 服务器支持此属性。它还用于配置基于 Spring Session servlet 的`SessionRepository`bean。

例如，如果您希望会话 cookie 具有`None`值的`SameSite`属性，您可以将以下内容添加到您的`application.properties`或`application.yaml`文件中：

```
server.servlet.session.cookie.same-site=none
```

如果您想更改`SameSite`添加到您的其他 cookie 上的属性`HttpServletResponse`，您可以使用`CookieSameSiteSupplier`. `CookieSameSiteSupplier`传递 a并`Cookie`可能返回一个`SameSite`值，或`null`。

您可以使用许多便利的工厂和过滤器方法来快速匹配特定的 cookie。例如，添加以下 bean 将自动为名称与正则表达式 `myapp.*`匹配的所有`cookie`应用 `Lax`的 `SameSite`。

```
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
​
    @Bean
    public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
        return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
    }
​
}
```

**字符编码**

可以使用配置`server.servlet.encoding.*`属性来配置用于请求和响应处理的嵌入式 Servlet 容器的字符编码行为。

当请求的`Accept-Language`标头指示请求的区域设置时，servlet 容器将自动将其映射到字符集。每个容器都提供默认区域设置到字符集映射，您应该验证它们是否满足您的应用程序的需求。如果不存在，请使用`server.servlet.encoding.mapping`配置属性来自定义映射，如以下示例所示：

```
server.servlet.encoding.mapping.ko=UTF-8
```

在前面的示例中，`ko`（韩语）区域设置已映射到`UTF-8`. 这相当于传统war部署文件`web.xml`中的`<locale-encoding-mapping-list>`条目。

**程序化定制**

如果您需要以编程方式配置嵌入式 servlet 容器，您可以注册一个实现`WebServerFactoryCustomizer`接口的 Spring bean。 `WebServerFactoryCustomizer`提供对`ConfigurableServletWebServerFactory` 的访问，其中包括许多自定义设置器方法。以下示例显示以编程方式设置端口：

```
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
​
    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }
​
}
```

`TomcatServletWebServerFactory`、`JettyServletWebServerFactory`和`UndertowServletWebServerFactory`是其专用变体，`ConfigurableServletWebServerFactory`分别为 Tomcat、Jetty 和 Undertow 提供额外的自定义 setter 方法。以下示例显示如何进行自定义`TomcatServletWebServerFactory`以提供对特定于 Tomcat 的配置选项的访问：

```
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
​
    @Override
    public void customize(TomcatServletWebServerFactory server) {
        server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
    }
​
}
```

**直接自定义 ConfigurableServletWebServerFactory**

对于需要从 扩展的更高级用例`ServletWebServerFactory`，您可以自己公开此类类型的 bean。

为许多配置选项提供了设置器。如果您需要做一些更奇特的事情，还提供了几个受保护的方法“挂钩”。详细信息请参见[源代码文档。](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/web/servlet/server/ConfigurableServletWebServerFactory.html)

> 自动配置的定制器仍然应用于您的定制工厂，因此请谨慎使用该选项。

**JSP 限制**

当运行使用嵌入式 servlet 容器（并打包为可执行存档）的 Spring Boot 应用程序时，JSP 支持存在一些限制。

* 对于 Jetty 和 Tomcat，如果您使用 war 包装，它应该可以工作。可执行的 war 在使用`java -jar` 启动时可以工作，并且也可以部署到任何标准容器。使用可执行 jar 时不支持 JSP。
* Undertow 不支持 JSP。
* 创建自定义`error.jsp`页面不会覆盖[错误处理](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.error-handling)的默认视图。 应改用[自定义错误页面。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.error-handling.error-pages)


# 8.2 反应式网络应用程序

Spring Boot 通过为 Spring Webflux 提供自动配置来简化响应式 Web 应用程序的开发。


# 8.2.1. “Spring WebFlux 框架”

Spring WebFlux 是 Spring Framework 5.0 中引入的新的响应式 Web 框架。与 Spring MVC 不同，它不需要 servlet API，完全异步且非阻塞，并通过[Reactor 项目实现](https://projectreactor.io/)[Reactive Streams](https://www.reactive-streams.org/)规范。

Spring WebFlux 有两种风格：函数式和基于注释的。基于注解的模型非常接近 Spring MVC 模型，如下例所示：

```java
@RestController
@RequestMapping("/users")
public class MyRestController {

    private final UserRepository userRepository;

    private final CustomerRepository customerRepository;

    public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
        this.userRepository = userRepository;
        this.customerRepository = customerRepository;
    }

    @GetMapping("/{userId}")
    public Mono<User> getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId);
    }

    @GetMapping("/{userId}/customers")
    public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
    }

    @DeleteMapping("/{userId}")
    public Mono<Void> deleteUser(@PathVariable Long userId) {
        return this.userRepository.deleteById(userId);
    }

}
```

WebFlux 是 Spring 框架的一部分，详细信息可在其[参考文档](https://docs.spring.io/spring-framework/reference/6.1/web/webflux.html)中找到。

“WebFlux.fn”，功能变体，将路由配置与请求的实际处理分开，如以下示例所示：

```java
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}
```

```java
@Component
public class MyUserHandler {

    public Mono<ServerResponse> getUser(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        ...
    }

}
```

“WebFlux.fn”是 Spring 框架的一部分，详细信息可在其[参考文档](https://docs.spring.io/spring-framework/reference/6.1/web/webflux-functional.html)中找到。

> 您可以定义任意数量的`RouterFunction`bean，以模块化路由器的定义。如果您需要应用优先级，可以对bean类排序。

首先，将该`spring-boot-starter-webflux`模块添加到您的应用程序中。

> 在应用程序中添加`spring-boot-starter-web`和`spring-boot-starter-webflux`模块会导致 Spring Boot 自动配置 Spring MVC，而不是 WebFlux。选择这种行为是因为许多 Spring 开发人员添加`spring-boot-starter-webflux`到他们的 Spring MVC 应用程序中以使用响应式`WebClient`. 您仍然可以通过将所选应用程序类型设置为 来强制执行您的选择`SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`。

**Spring WebFlux 自动配置**

Spring Boot 为 Spring WebFlux 提供自动配置，适用于大多数应用程序。

自动配置在 Spring 默认设置的基础上添加了以下功能：

* 为`HttpMessageReader`和`HttpMessageWriter`实例配置编解码器（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.reactive.webflux.httpcodecs)介绍）。
* 支持提供静态资源，包括支持 WebJars（[本文档稍后](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.static-content)介绍）。

如果您想保留 Spring Boot WebFlux 功能并添加额外的[WebFlux 配置](https://docs.spring.io/spring-framework/reference/6.1/web/webflux/config.html)，您可以添加自己的`@Configuration`type 类`WebFluxConfigurer`，但**不添加** `@EnableWebFlux`.

如果你想完全控制Spring WebFlux，你可以添加你自己的`@Configuration`注释`@EnableWebFlux`。

**Spring WebFlux 转换服务**

如果你想自定义`ConversionService`Spring WebFlux使用的，你可以提供一个`WebFluxConfigurer`带有方法的bean `addFormatters`。

还可以使用`spring.webflux.format.*`配置属性来自定义转换。如果未配置，则使用以下默认值：

| 财产                                | `DateTimeFormatter`                      |
| --------------------------------- | ---------------------------------------- |
| `spring.webflux.format.date`      | `ofLocalizedDate(FormatStyle.SHORT)`     |
| `spring.webflux.format.time`      | `ofLocalizedTime(FormatStyle.SHORT)`     |
| `spring.webflux.format.date-time` | `ofLocalizedDateTime(FormatStyle.SHORT)` |

**具有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器**

Spring WebFlux 使用`HttpMessageReader`和`HttpMessageWriter`接口来转换 HTTP 请求和响应。通过查看类路径中可用的库，将它们配置为具有合理的默认值的`CodecConfigurer`。

Spring Boot 为编解码器提供了专用的配置属性 `spring.codec.*`。它还通过使用 `CodecCustomizer` 实例来应用进一步的自定义。例如， `spring.jackson.*` 配置键应用于 Jackson 编解码器。

如果您需要添加或自定义编解码器，您可以创建自定义`CodecCustomizer`组件，如下例所示：

```java
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

    @Bean
    public CodecCustomizer myCodecCustomizer() {
        return (configurer) -> {
            configurer.registerDefaults(false);
            configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
            // ...
        };
    }

}
```

您还可以利用[Boot 的自定义 JSON 序列化器和反序列化器](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.json.jackson.custom-serializers-and-deserializers)。

**静态内容**

默认情况下，Spring Boot 从类路径中名为`/static`(或 `/public`或 `/resources`或`/META-INF/resources` )的目录提供静态内容。它使用Spring WebFlux的`ResourceWebHandler`，以便您可以通过添加自己的`WebFluxConfigurer`并覆盖`addResourceHandlers`方法来修改该行为。

默认情况下，资源映射到`/**`，但您可以通过设置`spring.webflux.static-path-pattern`属性来调整它。例如，将所有资源重新定位`/resources/**`可以实现如下：

```properties
spring.webflux.static-path-pattern=/resources/**
```

您还可以使用`spring.web.resources.static-locations` 自定义静态资源位置。这样做会用目录位置列表替换默认值。如果您这样做，默认欢迎页面检测将切换到您的自定义位置。因此，如果启动时在任何位置有`index.html`，则它是应用程序的主页。

除了前面列出的“标准”静态资源位置之外，还为[Webjars 内容](https://www.webjars.org/)制作了一个特殊情况。默认情况下，如果以 Webjars 格式打包，则任何路径为`/webjars/**` 的资源都将从 jar 文件提供。可以使用属性`spring.webflux.webjars-path-pattern`自定义路径。

> Spring WebFlux应用程序并不严格依赖于servlet API，因此它们不能部署为war文件并且不使用`src/main/webapp`目录。

**欢迎页面**

Spring Boot 支持静态和模板化欢迎页面。`index.html`它首先在配置的静态内容位置查找文件。如果没有找到，它就会寻找`index`模板。如果找到其中一个，它将自动用作应用程序的欢迎页面。

这仅充当应用程序定义的实际索引路由的后备。顺序由`HandlerMapping`bean 的顺序定义，默认情况如下：

| 映射bean                         | 备注                           |
| ------------------------------ | ---------------------------- |
| `RouterFunctionMapping`        | `RouterFunction`使用beans声明的端点 |
| `RequestMappingHandlerMapping` | `@Controller`bean中声明的端点      |
| `RouterFunctionMapping`欢迎页面    | 欢迎页面支持                       |

**模板引擎**

除了 REST Web 服务之外，您还可以使用 Spring WebFlux 来提供动态 HTML 内容。Spring WebFlux 支持多种模板技术，包括 Thymeleaf、FreeMarker 和 Mustache。

Spring Boot 包括对以下模板引擎的自动配置支持：

* [freemarker](https://freemarker.apache.org/docs/)
* [thymeleaf](https://www.thymeleaf.org/)
* [mustache](https://mustache.github.io/)

当您使用这些模板引擎之一和默认配置时，您的模板会自动从`src/main/resources/templates`加载.

**错误处理**

Spring Boot 提供了一种`WebExceptionHandler`以合理的方式处理所有错误的方法。它在处理顺序中的位置紧邻 WebFlux 提供的处理程序之前，这些处理程序被认为是最后的。对于机器客户端，它会生成一个 JSON 响应，其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端，有一个“whitelabel”错误处理程序，可以以 HTML 格式呈现相同的数据。您还可以提供自己的 HTML 模板来显示错误（请参阅[下一节](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.reactive.webflux.error-handling.error-pages)）。

在直接在 Spring Boot 中自定义错误处理之前，您可以利用Spring WebFlux 中的[RFC 7807 Problem Details](https://docs.spring.io/spring-framework/reference/6.1/web/webflux/ann-rest-exceptions.html)支持。Spring WebFlux 可以使用`application/problem+json`媒体类型生成自定义错误消息，例如：

```json
{
  "type": "https://example.org/problems/unknown-project",
  "title": "Unknown project",
  "status": 404,
  "detail": "No project found for id 'spring-unknown'",
  "instance": "/projects/spring-unknown"
}
```

可以通过设置`spring.webflux.problemdetails.enabled`为 `true`来启用此支持。

定制此功能的第一步通常涉及使用现有机制，但替换或增加错误内容。为此，您可以添加类型为`ErrorAttributes` 的 bean 。

要更改错误处理行为，您可以实现`ErrorWebExceptionHandler`并注册该类型的 bean 定义。由于 `ErrorWebExceptionHandler`是相当低级的，Spring Boot 还提供了一种方便的`AbstractErrorWebExceptionHandler`让您以 WebFlux 功能方式处理错误，如以下示例所示：

```java
@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties,
            ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
        super(errorAttributes, webProperties.getResources(), applicationContext);
        setMessageReaders(serverCodecConfigurer.getReaders());
        setMessageWriters(serverCodecConfigurer.getWriters());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
    }

    private boolean acceptsXml(ServerRequest request) {
        return request.headers().accept().contains(MediaType.APPLICATION_XML);
    }

    public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
        BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
        // ... additional builder calls
        return builder.build();
    }

}
```

为了获得更完整的图片，您还可以直接子类化`DefaultErrorWebExceptionHandler`并重写特定方法。

[在某些情况下，度量基础设施](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.metrics.supported.spring-webflux)不会记录在控制器或处理函数级别处理的错误。应用程序可以通过将已处理的异常设置为请求属性来确保此类异常与请求指标一起记录：

```java
@Controller
public class MyExceptionHandlingController {

    @GetMapping("/profile")
    public Rendering userProfile() {
        // ...
        throw new IllegalStateException();
    }

    @ExceptionHandler(IllegalStateException.class)
    public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
        exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
        return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
    }

}
```

**自定义错误页面**

如果要显示给定状态代码的自定义 HTML 错误页面，您可以添加从`error/*` 解析的视图，例如通过将文件添加到`/error`目录。错误页面可以是静态 HTML（即添加在任何静态资源目录下）或使用模板构建。文件的名称应该是准确的状态代码、状态代码系列掩码，或者如果没有其他匹配的情况则为默认值`error`。请注意，默认错误视图的路径是`error/error`，而对于 Spring MVC，默认错误视图的路径是`error`。

例如，要映射`404`到静态 HTML 文件，您的目录结构将如下所示：

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>
```

要使用 Mustache 模板映射所有`5xx`错误，您的目录结构将如下所示：

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.mustache
             +- <other templates>
```

**网页过滤器**

Spring WebFlux 提供了一个`WebFilter`可以实现过滤 HTTP 请求-响应交换的接口。 `WebFilter`在应用程序上下文中找到的 bean 将自动用于过滤每个交换。

如果过滤器的顺序很重要，则可以实现`Ordered`或注释`@Order`。Spring Boot 自动配置可以为您配置 Web 过滤器。执行此操作时，将使用下表中显示的顺序：

| 网页过滤器                       | 命令                               |
| --------------------------- | -------------------------------- |
| `WebFilterChainProxy`（春季安全） | `-100`                           |
| `HttpExchangesWebFilter`    | `Ordered.LOWEST_PRECEDENCE - 10` |


# 8.2.2. 嵌入式反应式服务器支持

Spring Boot 支持以下嵌入式反应式 Web 服务器：Reactor Netty、Tomcat、Jetty 和 Undertow。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下，嵌入式服务器在端口 8080 上侦听 HTTP 请求。

**定制反应式服务器**

常见的反应式 Web 服务器设置可以使用 Spring`Environment`属性进行配置。通常，您可以在您的`application.properties`或`application.yaml`文件中定义属性。

常见的服务器设置包括：

* 网络设置：传入 HTTP 请求的侦听端口 ( `server.port`)、要绑定的接口地址 ( `server.address`) 等。
* 错误管理：错误页面的位置（`server.error.path`）等。
* [SSL协议](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.configure-ssl)
* [HTTP 压缩](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.webserver.enable-response-compression)

Spring Boot 尝试尽可能多地公开通用设置，但这并不总是可行。对于这些情况，专用命名空间（例如`server.netty.*`提供特定于服务器的定制）。

> 请参阅[`ServerProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java)课程以获取完整列表。

**程序化定制**

如果您需要以编程方式配置反应式 Web 服务器，您可以注册一个实现 `WebServerFactoryCustomizer` 接口的 Spring bean。 `WebServerFactoryCustomizer` 提供对 `ConfigurableReactiveWebServerFactory` 的访问，其中包括许多自定义 setter 方法。以下示例显示以编程方式设置端口：

```java
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

    @Override
    public void customize(ConfigurableReactiveWebServerFactory server) {
        server.setPort(9000);
    }

}
```

`JettyReactiveWebServerFactory`、`NettyReactiveWebServerFactory`、`TomcatReactiveWebServerFactory`和`UndertowReactiveWebServerFactory`是它们的专用变体，`ConfigurableReactiveWebServerFactory`分别为 Jetty、Reactor Netty、Tomcat 和 Undertow 提供了额外的自定义 setter 方法。以下示例展示了如何进行自定义`NettyReactiveWebServerFactory`以提供对 Reactor Netty 特定配置选项的访问：

```java
@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

    @Override
    public void customize(NettyReactiveWebServerFactory factory) {
        factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
    }

}
```

**直接自定义 ConfigurableReactiveWebServerFactory**

对于需要从`ReactiveWebServerFactory` 扩展的更高级用例，您可以自己公开此类类型的 bean。

为许多配置选项提供了设置器。如果您需要做一些更奇特的事情，还提供了几个受保护的方法“挂钩”。详细信息请参见[源代码文档。](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/web/reactive/server/ConfigurableReactiveWebServerFactory.html)

> 自动配置的定制器仍然应用于您的定制工厂，因此请谨慎使用该选项。


# 8.2.3. 反应式服务器资源配置

自动配置 Reactor Netty 或 Jetty 服务器时，Spring Boot 将创建特定的 bean，为服务器实例提供 HTTP 资源：`ReactorResourceFactory`或`JettyResourceFactory`。

默认情况下，这些资源也将与 Reactor Netty 和 Jetty 客户端共享，以获得最佳性能，前提是：

* 服务器和客户端使用相同的技术
* 客户端实例是使用Spring Boot 自动配置的 bean构建的`WebClient.Builder`

`ReactorResourceFactory`开发人员可以通过提供自定义或bean来覆盖 Jetty 和 Reactor Netty 的资源配置`JettyResourceFactory`- 这将应用于客户端和服务器。

[您可以在WebClient 运行时部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.rest-client.webclient.runtime)了解有关客户端资源配置的更多信息。


# 8.3. 优雅关机

所有四种嵌入式 Web 服务器（Jetty、Reactor Netty、Tomcat 和 Undertow）以及反应式和基于 servlet 的 Web 应用程序均支持正常关闭。`SmartLifecycle`它作为关闭应用程序上下文的一部分发生，并在停止Bean的最早阶段执行。此停止处理使用超时，该超时提供宽限期，在此期间将允许完成现有请求，但不允许新请求。不允许新请求的具体方式取决于所使用的 Web 服务器。Jetty、Reactor Netty和Tomcat将停止接受网络层请求。Undertow 将接受请求，但立即响应服务不可用 (503)。

> 使用 Tomcat 正常关闭需要 Tomcat 9.0.33 或更高版本。

要启用正常关闭，请配置`server.shutdown`属性，如以下示例所示：

```
server.shutdown=graceful
```

要配置超时时间，请配置`spring.lifecycle.timeout-per-shutdown-phase`属性，如下例所示：

```
spring.lifecycle.timeout-per-shutdown-phase=20s
```

如果 IDE 未发送正确的`SIGTERM`信号，则使用正常关闭可能无法正常工作。有关更多详细信息，请参阅 IDE 的文档。


# 8.4. spring安全

如果[Spring Security](https://spring.io/projects/spring-security)位于类路径上，则默认情况下 Web 应用程序是安全的。Spring Boot 依赖 Spring Security 的内容协商策略来确定是否使用`httpBasic`或`formLogin`。要向 Web 应用程序添加方法级安全性，您还可以添加`@EnableGlobalMethodSecurity`所需的设置。[其他信息可以在Spring Security 参考指南](https://docs.spring.io/spring-security/reference/6.2/servlet/authorization/method-security.html)中找到。

默认情况`UserDetailsService`下只有一个用户。用户名是`user`，密码是随机的，并且在应用程序启动时以 WARN 级别打印，如下例所示：

```
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35

This generated password is for development use only. Your security configuration must be updated before running your application in production.
```

> 如果您微调日志记录配置，请确保`org.springframework.boot.autoconfigure.security`类别设置为日志`WARN`级别消息。否则，不会打印默认密码。

您可以通过提供`spring.security.user.name`和`spring.security.user.password`来更改用户名和密码。

Web 应用程序默认提供的基本功能包括：

* 具有内存存储的`UserDetailsService`（或者在 WebFlux 应用程序的情况下的`ReactiveUserDetailsService`）bean 和具有生成密码的单个用户（请参阅[`SecurityProperties.User`](https://docs.spring.io/spring-boot/docs/3.2.0/api/org/springframework/boot/autoconfigure/security/SecurityProperties.User.html) 参考资料 中的用户属性）。
* 整个应用程序（包括执行器端点，如果执行器位于类路径上）的基于表单的登录或 HTTP 基本安全性（取决于请求中的`Accept`标头）。
* 用于发布身份验证事件的`DefaultAuthenticationEventPublisher`。

您可以通过为其添加一个 bean 来提供不同的`AuthenticationEventPublisher`内容。


# 8.4.1. MVC安全

默认安全配置在`SecurityAutoConfiguration`和`UserDetailsServiceAutoConfiguration`中实现。 `SecurityAutoConfiguration`导入`SpringBootWebSecurityConfiguration`用于 Web 安全并且`UserDetailsServiceAutoConfiguration`配置身份验证，这在非 Web 应用程序中也相关。

要完全关闭默认的 Web 应用程序安全配置或组合多个 Spring Security 组件（例如 OAuth2 客户端和资源服务器），请添加`SecurityFilterChain` 类型的 bean （这样做不会禁用`UserDetailsService`配置或执行器的安全性）。要同时关闭`UserDetailsService`配置，您可以添加类型为`UserDetailsService`、`AuthenticationProvider`或`AuthenticationManager` 的bean 。

`UserDetailsService`的自动配置还将取消类路径上的以下任何 Spring Security 模块：

* `spring-security-oauth2-client`
* `spring-security-oauth2-resource-server`
* `spring-security-saml2-service-provider`

要在这些依赖项中的一个或多个之外使用 `UserDetailsService`，请定义您自己的 `InMemoryUserDetailsManager` bean。

可以通过添加自定义`SecurityFilterChain` bean 来覆盖访问规则。Spring Boot 提供了方便的方法，可用于覆盖执行器端点和静态资源的访问规则。 `EndpointRequest`可以用来创建一个基于`management.endpoints.web.base-path`属性的`RequestMatcher`。 `PathRequest`可用于在常用位置创建`RequestMatcher`资源。


# 8.4.2. WebFlux 安全

与 Spring MVC 应用程序类似，您可以通过添加`spring-boot-starter-security`依赖项来保护您的 WebFlux 应用程序。默认安全配置在`ReactiveSecurityAutoConfiguration`和`UserDetailsServiceAutoConfiguration`中实现。 `ReactiveSecurityAutoConfiguration`导入`WebFluxSecurityConfiguration`用于 Web 安全以及`UserDetailsServiceAutoConfiguration`配置身份验证，这在非 Web 应用程序中也相关。

要完全关闭默认的 Web 应用程序安全配置，您可以添加`WebFilterChainProxy`类型的 bean （这样做不会禁用`UserDetailsService`配置或 Actuator 的安全性）。要关闭`UserDetailsService`配置，您可以添加类型为`ReactiveUserDetailsService`或 `ReactiveAuthenticationManager`的bean 。

当以下任何 Spring Security 模块位于类路径上时，自动配置也会停止：

* `spring-security-oauth2-client`
* `spring-security-oauth2-resource-server`

要在这些依赖项中的一个或多个依赖项之外使用 `ReactiveUserDetailsService`，请定义您自己的 `MapReactiveUserDetailsService` bean。

访问规则和多个 Spring Security 组件（例如 OAuth 2 客户端和资源服务器）的使用可以通过添加自定义`SecurityWebFilterChain`bean 来配置。Spring Boot 提供了方便的方法，可用于覆盖执行器端点和静态资源的访问规则。 `EndpointRequest`可以用来创建一个基于`management.endpoints.web.base-path`属性的`ServerWebExchangeMatcher`。

`PathRequest`可用于在常用位置创建`ServerWebExchangeMatcher`资源。

例如，您可以通过添加以下内容来自定义安全配置：

```java
@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange((exchange) -> {
            exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
            exchange.pathMatchers("/foo", "/bar").authenticated();
        });
        http.formLogin(withDefaults());
        return http.build();
    }

}
```


# 8.4.3. OAuth2

[OAuth2](https://oauth.net/2/)是 Spring 支持的广泛使用的授权框架。

**客户端**

如果您的类路径上有`spring-security-oauth2-client`，您可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。此配置利用`OAuth2ClientProperties` 下的属性。相同的属性适用于 servlet 和反应式应用程序。

您可以在`spring.security.oauth2.client`前缀下注册多个 OAuth2 客户端和提供程序，如下例所示：

```properties
spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=Client for OpenID Connect
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
```

对于支持 [OpenID Connect discovery 的](https://openid.net/specs/openid-connect-discovery-1_0.html)OpenID Connect 提供商，可以进一步简化配置。提供者需要配置一个`issuer-uri`，该 URI 被断言为其发行者标识符。例如，如果`issuer-uri`提供的是“<https://example.com”，则将向“https://example.com/.well-known/openid-configuration”发出“OpenID> 提供商配置请求”。结果预计是“OpenID 提供商配置响应”。以下示例显示了如何使用以下命令配置 OpenID Connect 提供程序`issuer-uri`：

```properties
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
```

默认情况下，Spring Security的`OAuth2LoginAuthenticationFilter`仅处理匹配`/login/oauth2/code/*`的URL. 如果您想自定义`redirect-uri`以使用不同的模式，则需要提供配置来处理该自定义模式。例如，对于 Servlet 应用程序，您可以添加自己的应用`SecurityFilterChain`程序，如下所示：

```java
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MyOAuthClientConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .anyRequest().authenticated()
            )
            .oauth2Login((login) -> login
                .redirectionEndpoint((endpoint) -> endpoint
                    .baseUri("/login/oauth2/callback/*")
                )
            );
        return http.build();
    }

}
```

> Spring Boot 自动配置一个`InMemoryOAuth2AuthorizedClientService`以便Spring Security 用来管理客户端注册的文件。`InMemoryOAuth2AuthorizedClientService`功能有限，我们建议仅将其用于开发环境。对于生产环境，请考虑使用`JdbcOAuth2AuthorizedClientService`或`OAuth2AuthorizedClientService`创建您自己的.

**常见提供商的 OAuth2 客户端注册**

对于常见的 OAuth2 和 OpenID 提供商（包括 Google、Github、Facebook 和 Okta），我们提供了一组提供商默认值（分别为`google`、`github`、`facebook`、 和`okta`）。

如果您不需要自定义这些提供程序，则可以将`provider`属性设置为需要推断默认值的提供程序。此外，如果客户端注册的密钥与默认支持的提供程序匹配，Spring Boot 也会推断出这一点。

换句话说，以下示例中的两个配置都使用 Google 提供程序：

```properties
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password
```

**资源服务器**

如果您的类路径上有`spring-security-oauth2-resource-server`，Spring Boot 可以设置 OAuth2 资源服务器。对于 JWT 配置，需要指定 JWK Set URI 或 OIDC Issuer URI，如以下示例所示：

```properties
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
```

```properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
```

> 如果授权服务器不支持 JWK Set URI，您可以使用用于验证 JWT 签名的公钥配置资源服务器。这可以使用`spring.security.oauth2.resourceserver.jwt.public-key-location` 属性来完成，其中值需要指向包含 PEM 编码的 x509 格式的公钥的文件。

`spring.security.oauth2.resourceserver.jwt.audiences`属性可用于指定 JWT 中 aud 声明的预期值。例如，要求 JWT 包含值为 `my-audience`的 aud 声明：

```properties
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience
```

相同的属性适用于 servlet 和反应式应用程序。或者，您可以为 servlet 应用程序或`ReactiveJwtDecoder`反应式应用程序定义自己的`JwtDecoder` bean。

如果使用不透明令牌而不是 JWT，您可以配置以下属性以通过内省验证令牌：

```properties
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret
```

同样，相同的属性适用于 servlet 和反应式应用程序。或者，您可以为 servlet 应用程序或`ReactiveOpaqueTokenIntrospector`反应式应用程序定义自己的`OpaqueTokenIntrospector` bean。

**授权服务器**

如果您的类路径上有`spring-security-oauth2-authorization-server`，则可以利用一些自动配置来设置基于 Servlet 的 OAuth2 授权服务器。

您可以在`spring.security.oauth2.authorizationserver.client`前缀下注册多个 OAuth2 客户端，如下例所示：

```properties
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-id=abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-secret={noop}secret1
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-authentication-methods[0]=client_secret_basic
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[0]=authorization_code
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[1]=refresh_token
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[0]=https://my-client-1.com/login/oauth2/code/abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[1]=https://my-client-1.com/authorized
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[0]=openid
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[1]=profile
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[2]=email
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[3]=phone
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[4]=address
spring.security.oauth2.authorizationserver.client.my-client-1.require-authorization-consent=true
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-id=efgh
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-secret={noop}secret2
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-authentication-methods[0]=client_secret_jwt
spring.security.oauth2.authorizationserver.client.my-client-2.registration.authorization-grant-types[0]=client_credentials
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[0]=user.read
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[1]=user.write
spring.security.oauth2.authorizationserver.client.my-client-2.jwk-set-uri=https://my-client-2.com/jwks
spring.security.oauth2.authorizationserver.client.my-client-2.token-endpoint-authentication-signing-algorithm=RS256
```

> `client-secret` 属性的格式必须能够与配置的 `PasswordEncoder` 匹配。 `PasswordEncoder` 的默认实例是通过`PasswordEncoderFactories.createDelegatingPasswordEncoder()` 创建的。

Spring Boot 为 Spring Authorization Server 提供的自动配置旨在快速入门。大多数应用程序都需要定制，并且需要定义多个 bean 来覆盖自动配置。

以下组件可以定义为 bean 来覆盖特定于 Spring Authorization Server 的自动配置：

* `RegisteredClientRepository`
* `AuthorizationServerSettings`
* `SecurityFilterChain`
* `com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>`
* `JwtDecoder`

  Spring Boot 自动配置的`InMemoryRegisteredClientRepository`其只能被Spring 授权服务器用于管理注册客户端的权限。`InMemoryRegisteredClientRepository`功能有限，我们建议仅将其用于开发环境。对于生产环境，请考虑使用`JdbcRegisteredClientRepository`或创建您自己的`RegisteredClientRepository`.

[其他信息可以在《Spring 授权服务器参考指南》](https://docs.spring.io/spring-authorization-server/reference/1.2/index.html)的[“入门”](https://docs.spring.io/spring-authorization-server/reference/1.2/getting-started.html)一章中找到。


# 8.4.4. SAML 2.0

**依赖方**

如果您的类路径上有`spring-security-saml2-service-provider`，您可以利用一些自动配置来设置 SAML 2.0 依赖方。此配置利用`Saml2RelyingPartyProperties` 下的属性。

依赖方注册代表身份提供商 IDP 和服务提供商 SP 之间的配对配置。您可以在`spring.security.saml2.relyingparty`前缀下注册多个依赖方，如下例所示：

```properties
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.response-url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.binding=POST
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.sso-url=https://remoteidp1.sso.url

spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST
```

对于 SAML2 注销，默认情况下，Spring Security`Saml2LogoutRequestFilter`和`Saml2LogoutResponseFilter`只处理与`/logout/saml2/slo`匹配的 URL 。如果您想要自定义 AP 发起的注销请求发送到的 url 或 AP 发送注销响应的响应 url，以使用不同的模式，您需要提供配置来处理该自定义模式。例如，对于 Servlet 应用程序，您可以添加自己的应用`SecurityFilterChain`程序，如下所示：

```java
@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
        http.saml2Login(withDefaults());
        http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
            .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
        return http.build();
    }

}
```


# 8.5. spring 会话

Spring Boot为各种数据存储提供[Spring Session自动配置。](https://spring.io/projects/spring-session)构建 servlet Web 应用程序时，可以自动配置以下存储：

* Redis
* JDBC
* Hazelcast
* MongoDB

此外，[Spring Boot for Apache Geode](https://github.com/spring-projects/spring-boot-data-geode)提供了[使用 Apache Geode 作为会话存储的自动配置](https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5/#geode-session)。

Servlet 自动配置取代了使用`@Enable*HttpSession`.

如果类路径上存在单个 Spring Session 模块，Spring Boot 会自动使用该存储实现。如果您有多个实现，Spring Boot 将使用以下顺序来选择特定实现：

1. Redis
2. JDBC
3. Hazelcast
4. MongoDB
5. 如果 Redis、JDBC、Hazelcast 和 MongoDB 都不可用，我们不会配置`SessionRepository`.

构建响应式 Web 应用程序时，可以自动配置以下存储：

* Redis
* MongoDB

反应式自动配置取代了使用`@Enable*WebSession`.

与 servlet 配置类似，如果您有多个实现，Spring Boot 将使用以下顺序来选择特定实现：

1. Redis
2. MongoDB
3. 如果 Redis 和 MongoDB 都不可用，我们不会配置`ReactiveSessionRepository`.

每个存储都有特定的附加设置。例如，可以自定义 JDBC 存储的表名称，如以下示例所示：

```
spring.session.jdbc.table-name=SESSIONS
```

要设置会话超时，您可以使用`spring.session.timeout`属性。如果未使用 servlet Web 应用程序设置`server.servlet.session.timeout`属性，则自动配置将回退到 的值。

您可以使用`@Enable*HttpSession`(servlet) 或`@Enable*WebSession`(reactive)来控制 Spring Session 的配置。这将导致自动配置停止。然后可以使用注释的属性而不是前面描述的配置属性来配置 Spring Session。


# 8.6.GraphQL

如果您想构建 GraphQL 应用程序，可以利用 Spring Boot 对[Spring for GraphQL](https://spring.io/projects/spring-graphql)的自动配置。Spring for GraphQL 项目基于[GraphQL Java](https://github.com/graphql-java/graphql-java)。您至少需要`spring-boot-starter-graphql`启动器。由于 GraphQL 与传输无关，因此您还需要在应用程序中添加一个或多个额外的启动器才能通过网络公开您的 GraphQL API：

| 启动器                             | 传输方式       | 底层实现                            |
| ------------------------------- | ---------- | ------------------------------- |
| `spring-boot-starter-web`       | HTTP协议     | 春季MVC                           |
| `spring-boot-starter-websocket` | WebSocket  | 用于 Servlet 应用程序的 WebSocket      |
| `spring-boot-starter-webflux`   | HTTP、网络套接字 | Spring WebFlux                  |
| `spring-boot-starter-rsocket`   | TCP、网络套接字  | Reactor Netty 上的 Spring WebFlux |

**8.6.1. GraphQL Schema**

Spring GraphQL 应用程序在启动时需要一个定义的模式。默认情况下，您可以在`src/main/resources/graphql/**`下面写入“.graphqls”或“.gqls”模式文件，Spring Boot 将自动选择它们。您可以使用`spring.graphql.schema.locations`自定义位置并使用`spring.graphql.schema.file-extensions`定义文件扩展名。

> 如果您希望 Spring Boot 检测所有应用程序模块中的架构文件以及该位置的依赖项，您可以设置`spring.graphql.schema.locations`为`"classpath*:graphql/**/"`（注意`classpath*:`前缀）。

在以下部分中，我们将考虑这个示例 GraphQL 模式，定义两种类型和两个查询：

```
type Query {
    greeting(name: String! = "Spring"): String!
    project(slug: ID!): Project
}
​
""" A Project in the Spring portfolio """
type Project {
    """ Unique string id used in URLs """
    slug: ID!
    """ Project name """
    name: String!
    """ URL of the git repository """
    repositoryUrl: String!
    """ Current support status """
    status: ProjectStatus!
}
​
enum ProjectStatus {
    """ Actively supported by the Spring team """
    ACTIVE
    """ Supported by the community """
    COMMUNITY
    """ Prototype, not officially supported yet  """
    INCUBATING
    """ Project being retired, in maintenance mode """
    ATTIC
    """ End-Of-Lifed """
    EOL
}
```

默认情况下，模式上将允许进行[字段自省，因为这是 GraphiQL 等工具所必需的。](https://spec.graphql.org/draft/#sec-Introspection)如果您不希望公开有关架构的信息，可以通过设置`spring.graphql.schema.introspection.enabled`为 `false`来禁用自省。

**8.6.2. GraphQL 运行时接线**

GraphQL Java`RuntimeWiring.Builder`可用于注册自定义标量类型、指令、类型解析器`DataFetcher`等。您可以在 Spring 配置中声明`RuntimeWiringConfigurer` beans 以访问`RuntimeWiring.Builder`. Spring Boot 检测此类 bean 并将它们添加到[GraphQlSource builder](https://docs.spring.io/spring-graphql/reference/1.2/#execution-graphqlsource)中。

然而，通常情况下，应用程序不会直接实现`DataFetcher`，而是创建[带注释的控制器](https://docs.spring.io/spring-graphql/reference/1.2/#controllers)。Spring Boot 将自动检测带有`@Controller`注释处理方法的类并将其注册为`DataFetcher`。以下是我们的问候语查询的`@Controller`类实现示例：

```
@Controller
public class GreetingController {
​
    @QueryMapping
    public String greeting(@Argument String name) {
        return "Hello, " + name + "!";
    }
​
}
```

**8.6.3. Querydsl 和 QueryByExample 存储库支持**

Spring Data 提供对 Querydsl 和 QueryByExample 存储库的支持。Spring GraphQL 可以[将 Querydsl 和 QueryByExample 存储库配置为`DataFetcher`](https://docs.spring.io/spring-graphql/reference/1.2/#data).

Spring Data 存储库注释`@GraphQlRepository`并扩展以下之一：

* `QuerydslPredicateExecutor`
* `ReactiveQuerydslPredicateExecutor`
* `QueryByExampleExecutor`
* `ReactiveQueryByExampleExecutor`

由 Spring Boot 检测到并视为`DataFetcher`匹配顶级查询的候选者。

**8.6.4. 传输**

**HTTP 和 WebSocket**

默认情况下，GraphQL HTTP 端点位于 HTTP POST `/graphql`。可以使用`spring.graphql.path` 自定义路径。

> Spring MVC 和 Spring WebFlux 的 HTTP 端点均由 `@Order` 为 0 的 RouterFunction bean 提供。如果您定义自己的 `RouterFunction` bean，则可能需要添加适当的 `@Order` 注释以确保它们正确排序。

GraphQL WebSocket 端点默认处于关闭状态。要启用它：

* 对于 Servlet 应用程序，添加 WebSocket 启动器`spring-boot-starter-websocket`
* 对于 WebFlux 应用程序，不需要额外的依赖项
* 对于两者，都必须设置应用程序属性`spring.graphql.websocket.path`

Spring GraphQL 提供了[Web 拦截](https://docs.spring.io/spring-graphql/reference/1.2/#web-interception)模型。这对于从 HTTP 请求标头检索信息并将其设置在 GraphQL 上下文中或从同一上下文中获取信息并将其写入响应标头非常有用。使用 Spring Boot，您可以声明一个`WebInterceptor`bean 以将其注册到 Web 传输。

[Spring MVC](https://docs.spring.io/spring-framework/reference/6.1/web/webmvc-cors.html)和[Spring WebFlux](https://docs.spring.io/spring-framework/reference/6.1/web/webflux-cors.html)支持 CORS（跨源资源共享）请求。CORS 是 GraphQL 应用程序 Web 配置的关键部分，可从使用不同域的浏览器访问这些应用程序。

Spring Boot 支持`spring.graphql.cors.*`命名空间下的多种配置属性；这是一个简短的配置示例：

```
spring.graphql.cors.allowed-origins=https://example.org
spring.graphql.cors.allowed-methods=GET,POST
spring.graphql.cors.max-age=1800s
```

**RSocket**

RSocket 还支持作为 WebSocket 或 TCP 之上的传输。配置 RSocket 服务器后，我们可以使用 `spring.graphql.rsocket.mapping`在特定路由上配置 GraphQL 处理程序。例如，将该映射配置为“graphql”意味着我们可以在使用 `RSocketGraphQlClient` 发送请求时将其用作路由。

Spring Boot 自动配置一个`RSocketGraphQlClient.Builder<?>`可以注入到组件中的 bean：

```
@Component
public class RSocketGraphQlClientExample {
​
    private final RSocketGraphQlClient graphQlClient;
​
    public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder<?> builder) {
        this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build();
    }
```

然后发送请求：

```
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
    .retrieve("bookById")
    .toEntity(Book.class);
```

**8.6.5. 异常处理**

Spring GraphQL 使应用程序能够注册一个或多个按顺序调用的 Spring `DataFetcherExceptionResolver`组件。异常必须解析为`graphql.GraphQLError`对象列表，请参阅[Spring GraphQL 异常处理文档](https://docs.spring.io/spring-graphql/reference/1.2/#execution-exceptions)。Spring Boot 将自动检测`DataFetcherExceptionResolver`bean 并将它们注册到`GraphQlSource.Builder`.

**8.6.6. GraphiQL 和 Schema 打印**

Spring GraphQL 提供了基础设施来帮助开发人员使用或开发 GraphQL API。

Spring GraphQL 附带一个默认的 GraphiQL 页面，默认情况下在“`/graphiql`”处公开。该页面默认处于禁用状态，可以使用 `spring.graphql.graphiql.enabled`属性打开。许多公开此类页面的应用程序更喜欢自定义构建。默认实现在开发过程中非常有用，这就是为什么它在开发过程中通过`spring-boot-devtools` 自动公开。

您还可以选择在启用`spring.graphql.schema.printer.enabled`属性，以`/graphql/schema`文本格式公开 GraphQL Schema 。


# 8.7. Spring HATEOAS

如果您开发使用超媒体的 RESTful API，Spring Boot 会为 Spring HATEOAS 提供自动配置，该配置适用于大多数应用程序。自动配置取代了使用 `@EnableHypermediaSupport` 的需要，并注册了许多 bean 来简化构建基于超媒体的应用程序，包括 LinkDiscoverers（用于客户端支持）和配置为正确将响应编组到所需表示的 `ObjectMapper`。 `ObjectMapper`是通过设置各种 `spring.jackson.*` 属性来定制的，或者如果存在的话，可以通过`Jackson2ObjectMapperBuilder` bean 来定制。

您可以使用 `@EnableHypermediaSupport`来控制 Spring HATEOAS 的配置。请注意，这样做会禁用前面描述的 ObjectMapper 自定义。

`spring-boot-starter-hateoas` 特定于 Spring MVC，不应与 Spring WebFlux 结合使用。为了将 Spring HATEOAS 与 Spring WebFlux 一起使用，您可以添加对 `org.springframework.hateoas:spring-hateoas` 的直接依赖项以及 `spring-boot-starter-webflux`。

默认情况下，接受 `application/json` 的请求将收到`application/hal+json`响应。要禁用此行为，请将`spring.hateoas.use-hal-as-default-json-media-type` 设置为 false 并定义`HypermediaMappingInformation`或 `HalConfiguration`来配置 Spring HATEOAS 以满足应用程序及其客户端的需求。

<br>


# 8.8.接下来读什么

&#x20;您现在应该很好地了解如何使用 Spring Boot 开发 Web 应用程序。接下来的几节将介绍 Spring Boot 如何与各种数据技术、消息系统和其他 IO 功能集成。您可以根据应用程序的需求选择其中任何一个。


# 9. 数据

Spring Boot 与多种数据技术集成，包括 SQL 和 NoSQL。

Spring Boot 与多种数据技术集成，包括 SQL 和 NoSQL。


# 9.1. SQL数据库

Spring[框架](https://spring.io/projects/spring-framework)为使用 SQL 数据库提供了广泛的支持，从使用直接 JDBC 访问`JdbcClient`或`JdbcTemplate`完成“对象关系映射”技术（例如 Hibernate）。 [Spring Data](https://spring.io/projects/spring-data)提供了额外级别的功能：`Repository`直接从接口创建实现并使用约定从方法名称生成查询。

**9.1.1. 配置数据源**

Java 的`javax.sql.DataSource`接口提供了使用数据库连接的标准方法。传统上，`DataSource`使用 `URL`以及一些凭据来建立数据库连接。

> 请参阅[“操作方法”部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-access.configure-custom-datasource)以获取更高级的示例，通常是为了完全控制数据源的配置。

**嵌入式数据库支持**

使用内存嵌入式数据库开发应用程序通常很方便。显然，内存数据库不提供持久存储。您需要在应用程序启动时填充数据库，并准备好在应用程序结束时丢弃数据。

> “操作方法”部分包括[有关如何初始化数据库的部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-initialization)。

Spring Boot 可以自动配置嵌入式[H2](https://www.h2database.com/)、[HSQL](https://hsqldb.org/)和[Derby](https://db.apache.org/derby/)数据库。您无需提供任何连接 URL。您只需包含对要使用的嵌入式数据库的构建依赖项。如果类路径上有多个嵌入式数据库，请设置`spring.datasource.embedded-database-connection`配置属性来控制使用哪一个。将属性设置为`none`禁用嵌入式数据库的自动配置。

> 如果您在测试中使用此功能，您可能会注意到，无论您使用的应用程序上下文数量如何，整个测试套件都会重复使用同一个数据库。如果您想确保每个上下文都有一个单独的嵌入式数据库，您应该设置`spring.datasource.generate-unique-name`为`true`。

例如，典型的 POM 依赖关系如下：

```
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>
```

> 您需要依赖`spring-jdbc`嵌入式数据库才能自动配置。在此示例中，它是通过 `spring-boot-starter-data-jpa`传递地拉入的。
>
> 如果出于某种原因，您确实为嵌入式数据库配置了连接 URL，请注意确保禁用数据库的自动关闭功能。如果你使用H2，你应该设置`DB_CLOSE_ON_EXIT=FALSE`。如果您使用HSQLDB，您应该确保不使用`shutdown=true`。禁用数据库的自动关闭可以让 Spring Boot 控制数据库何时关闭，从而确保在不再需要访问数据库时关闭数据库。

**连接到生产数据库**

还可以使用池来自动配置生产数据库连接`DataSource`。

**数据源配置**

数据源配置由`spring.datasource.*`. 例如，您可以在`application.properties` 中声明以下部分：

```
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
```

> 您至少应该通过设置`spring.datasource.url`属性来指定 URL。否则，Spring Boot 会尝试自动配置嵌入式数据库
>
> Spring Boot 可以从 URL 推断出大多数数据库的 JDBC 驱动程序类。如果需要指定特定的类，可以使用该`spring.datasource.driver-class-name`属性。
>
> 为了创建池`DataSource`，我们需要能够验证有效的`Driver`类是否可用，因此我们在执行任何操作之前进行检查。换句话说，如果您设置了`spring.datasource.driver-class-name=com.mysql.jdbc.Driver`，那么该类必须是可加载的。

请参阅[`DataSourceProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java)参考资料 了解更多支持的选项。这些是无论[实际实施](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.sql.datasource.connection-pool)如何都有效的标准选项。还可以通过使用各自的前缀（`spring.datasource.hikari.*` 、`spring.datasource.tomcat.*`、`spring.datasource.dbcp2.*`和`spring.datasource.oracleucp.*`）来微调特定于实现的设置。有关更多详细信息，请参阅您正在使用的连接池实现的文档。

例如，如果您使用[Tomcat 连接池](https://tomcat.apache.org/tomcat-10.1-doc/jdbc-pool.html#Common_Attributes)，则可以自定义许多其他设置，如以下示例所示：

```
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.test-on-borrow=true
```

如果没有可用连接，这会将池设置为在抛出异常之前等待 10000 毫秒，将最大连接数限制为 50，并在从池中借用连接之前验证连接。

**支持的连接池**

Spring Boot 使用以下算法来选择特定的实现：

1. 我们更喜欢[HikariCP](https://github.com/brettwooldridge/HikariCP)的性能和并发性。如果 HikariCP 可用，我们总是选择它。
2. 否则，如果 Tomcat 池`DataSource`可用，我们就使用它。
3. 否则，如果[Commons DBCP2](https://commons.apache.org/proper/commons-dbcp/)可用，我们将使用它。
4. 如果 HikariCP、Tomcat 和 DBCP2 都不可用，而 Oracle UCP 可用，则我们使用它。

> 如果您使用`spring-boot-starter-jdbc`或`spring-boot-starter-data-jpa`“starters”，您会自动获得对`HikariCP`.

您可以完全绕过该算法并通过设置`spring.datasource.type`属性来指定要使用的连接池。如果您在 Tomcat 容器中运行应用程序，这一点尤其重要，因为默认提供 `tomcat-jdbc`。

始终可以使用`DataSourceBuilder`添加任何连接池. 如果您定义自己的`DataSource`bean，则不会发生自动配置。`DataSourceBuilder`支持以下连接池：

* HikariCP
* Tomcat 池化`Datasource`
* Commons DBCP2
* Oracle UCP 和`OracleDataSource`
* Spring框架的`SimpleDriverDataSource`
* H2`JdbcDataSource`
* PostgreSQL`PGSimpleDataSource`
* C3P0

**连接到 JNDI 数据源**

如果将 Spring Boot 应用程序部署到应用程序服务器，您可能希望使用应用程序服务器的内置功能来配置和管理数据源，并使用 JNDI 访问它。

`spring.datasource.jndi-name`属性可用作`spring.datasource.url`、`spring.datasource.username`和`spring.datasource.password`属性的替代，以从特定 JNDI 位置访问`DataSource`。例如，以下部分`application.properties`显示了如何访问 JBoss AS 定义的`DataSource`：

```
spring.datasource.jndi-name=java:jboss/datasources/customers
```

**9.1.2. 使用 JdbcTemplate**

Spring`JdbcTemplate`和`NamedParameterJdbcTemplate`类是自动配置的，您可以使用`@Autowire`将它们直接添加到您自己的 bean 中，如以下示例所示：

```
@Component
public class MyBean {
​
    private final JdbcTemplate jdbcTemplate;
​
    public MyBean(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
​
    public void doSomething() {
        this.jdbcTemplate ...
    }
​
}
```

您可以通过属性来自定义模板的一些属性`spring.jdbc.template.*`，如下例所示：

```
spring.jdbc.template.max-rows=500
```

> `NamedParameterJdbcTemplate` 在幕后重用相同的`JdbcTemplate` 实例。如果定义了多个`JdbcTemplate`并且不存在主要候选者，则不会自动配置 `NamedParameterJdbcTemplate`。

**9.1.3. 使用 JdbcClient**

Spring`JdbcClient`是根据`NamedParameterJdbcTemplate`自动配置的. 您也可以将其直接注入到您自己的 bean 中，如以下示例所示：

```
@Component
public class MyBean {
​
    private final JdbcClient jdbcClient;
​
    public MyBean(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }
​
    public void doSomething() {
        this.jdbcClient ...
    }
​
}
```

如果您依靠自动配置来创建底层 JdbcTemplate，则客户端也会考虑使用 `spring.jdbc.template.*`属性的任何自定义。

**9.1.4. JPA 和 Spring Data JPA**

Java Persistence API 是一种标准技术，可让您将对象“映射”到关系数据库。`spring-boot-starter-data-jpa`POM提供了一种快速入门方法。它提供了以下关键依赖项：

* Hibernate：最流行的 JPA 实现之一。
* Spring Data JPA：帮助您实现基于 JPA 的存储库。
* Spring ORM：Spring 框架的核心 ORM 支持。

> 我们在这里不过多讨论 JPA 或[Spring Data](https://spring.io/projects/spring-data)的细节。您可以遵循[spring.io中的](https://spring.io/)[“使用 JPA 访问数据”](https://spring.io/guides/gs/accessing-data-jpa/)指南并阅读[Spring Data JPA](https://spring.io/projects/spring-data-jpa)和[Hibernate](https://hibernate.org/orm/documentation/)参考文档。

**实体类**

传统上，JPA“实体”类是在`persistence.xml`文件中指定的。对于 Spring Boot，这个文件不是必需的，而是使用“Entity Scanning”。默认情况下会扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)

任何用`@Entity`、`@Embeddable`、 或`@MappedSuperclass`注解的类都会被考虑。典型的实体类类似于以下示例：

```
@Entity
public class City implements Serializable {
​
    @Id
    @GeneratedValue
    private Long id;
​
    @Column(nullable = false)
    private String name;
​
    @Column(nullable = false)
    private String state;
​
    // ... additional members, often include @OneToMany mappings
​
    protected City() {
        // no-args constructor required by JPA spec
        // this one is protected since it should not be used directly
    }
​
    public City(String name, String state) {
        this.name = name;
        this.state = state;
    }
​
    public String getName() {
        return this.name;
    }
​
    public String getState() {
        return this.state;
    }
​
    // ... etc
​
}
```

> 您可以使用`@EntityScan`注释自定义实体扫描位置。请参阅“[从 Spring 配置中分离 @Entity 定义](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-access.separate-entity-definitions-from-spring-configuration)”操作方法。

**Spring Data JPA 存储库**

[Spring Data JPA](https://spring.io/projects/spring-data-jpa)存储库是您可以定义来访问数据的接口。JPA 查询是根据您的方法名称自动创建的。例如，`CityRepository`接口可能声明一个`findAllByState(String state)`方法来查找给定州的所有城市。

对于更复杂的查询，您可以使用 Spring Data 的[`Query`](https://docs.spring.io/spring-data/jpa/docs/3.2.0/api/org/springframework/data/jpa/repository/Query.html)注释来注释您的方法。

Spring Data 存储库通常从[`Repository`](https://docs.spring.io/spring-data/commons/docs/3.2.0/api/org/springframework/data/repository/Repository.html)或[`CrudRepository`](https://docs.spring.io/spring-data/commons/docs/3.2.0/api/org/springframework/data/repository/CrudRepository.html)接口扩展。如果您使用自动配置，则会[在自动配置包中](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)搜索存储库。

> 您可以使用 `@EnableJpaRepositories`自定义位置来查找存储库。

以下示例显示了典型的 Spring Data 存储库接口定义：

```
public interface CityRepository extends Repository<City, Long> {
​
    Page<City> findAll(Pageable pageable);
​
    City findByNameAndStateAllIgnoringCase(String name, String state);
​
}
```

Spring Data JPA 存储库支持三种不同的引导模式：默认、延迟和懒加载。要启用延迟或懒加载引导，请分别将该`spring.data.jpa.repositories.bootstrap-mode`属性设置为`deferred`或`lazy`。当使用延迟或惰性引导时，自动配置`EntityManagerFactoryBuilder`将使用上下文`AsyncTaskExecutor`（如果有）作为引导执行器。如果存在多个，则将使用`applicationTaskExecutor`指定的那个。

> 使用延迟或惰性引导时，请确保在应用程序上下文引导阶段之后延迟对 JPA 基础结构的任何访问。您可以使用它`SmartInitializingSingleton`来调用任何需要 JPA 基础结构的初始化。对于作为 Spring bean 创建的 JPA 组件（例如转换器），请使用`ObjectProvider`延迟解析依赖项（如果有）。
>
> 我们仅仅触及了 Spring Data JPA 的皮毛。有关完整的详细信息，请参阅[Spring Data JPA 参考文档](https://docs.spring.io/spring-data/jpa/reference/3.2.0/)。

**Spring Data Envers 存储库**

如果[Spring Data Envers](https://spring.io/projects/spring-data-envers)可用，JPA 存储库会自动配置为支持典型的 Envers 查询。

要使用 Spring Data Envers，请确保您的存储库扩展`RevisionRepository`如同以下示例：

```
public interface CountryRepository extends RevisionRepository<Country, Long, Integer>, Repository<Country, Long> {
​
    Page<Country> findAll(Pageable pageable);
​
}
```

> 有关更多详细信息，请查看[Spring Data Envers 参考文档](https://docs.spring.io/spring-data/jpa/reference/3.2.0//#envers)。

**创建和删除 JPA 数据库**

默认情况下，仅当您使用嵌入式数据库（H2、HSQL 或 Derby）时才会自动创建 JPA 数据库。您可以使用`spring.jpa.*`属性显式配置 JPA 设置。例如，要创建和删除表，您可以将以下行添加到您的`application.properties`：

```
spring.jpa.hibernate.ddl-auto=create-drop
```

> Hibernate 自己的内部属性名称（如果您记得更好的话）是`hibernate.hbm2ddl.auto`。您可以通过使用`spring.jpa.properties.*`（在将它们添加到实体管理器之前删除前缀）来设置它以及其他 Hibernate 本机属性。以下行显示了为 Hibernate 设置 JPA 属性的示例：

```
spring.jpa.properties.hibernate[globally_quoted_identifiers]=true
```

前面示例中的行将`hibernate.globally_quoted_identifiers`属性的`true`值传递给Hibernate 实体管理器。

默认情况下，DDL 执行（或验证）被推迟到`ApplicationContext`开始为止。还有一个`spring.jpa.generate-ddl`标志，但如果 Hibernate 自动配置处于活动状态，则不会使用它，因为`ddl-auto`设置了更细粒度。

**在视图中打开EntityManager**

如果您正在运行 Web 应用程序，Spring Boot 默认情况下会注册[`OpenEntityManagerInViewInterceptor`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.html)以应用“在视图中打开 EntityManager”模式，以允许在 Web 视图中延迟加载。如果您不希望出现这种行为，您应该在您的`application.properties`将`spring.jpa.open-in-view`设置为`false`.

**9.1.5. Spring Data JDBC**

Spring Data 包括对 JDBC 的存储库支持，并将自动为`CrudRepository`. 对于更高级的查询，提供了`@Query`注释。

当类路径上有必要的依赖项时，Spring Boot 将自动配置 Spring Data 的 JDBC 存储库。可以将它们添加到您的项目中，并仅依赖于`spring-boot-starter-data-jdbc`. 如有必要，您可以通过向应用程序添加`@EnableJdbcRepositories`注释或`AbstractJdbcConfiguration`子类来控制 Spring Data JDBC 的配置。

> 有关 Spring Data JDBC 的完整详细信息，请参阅[参考文档](https://docs.spring.io/spring-data/jdbc/docs/3.2.0/reference/html/)。

**9.1.6. 使用 H2 的 Web 控制台**

[H2 数据库](https://www.h2database.com/)提供了一个[基于浏览器的控制台](https://www.h2database.com/html/quickstart.html#h2_console)，Spring Boot 可以为您自动配置。当满足以下条件时，控制台会自动配置：

* 您正在开发一个基于 servlet 的 Web 应用程序。
* 位于类路径上的`com.h2database:h2`。
* 您正在使用[Spring Boot 的开发者工具](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.devtools)。

> 如果您不使用 Spring Boot 的开发人员工具，但仍想使用 H2 的控制台，则可以将`spring.h2.console.enabled`属性配置为`true`。
>
> H2 控制台仅供开发期间使用，因此您应注意确保在生产中`spring.h2.console.enabled`未将其设置为`true`。

**更改 H2 控制台的路径**

默认情况下，控制台位于`/h2-console`。您可以使用`spring.h2.console.path`属性自定义控制台的路径。

**在安全应用程序中访问 H2 控制台**

H2 Console 使用框架，仅用于开发，没有实施 CSRF 保护措施。如果您的应用程序使用 Spring Security，则需要将其配置为

* 对针对控制台的请求禁用 CSRF 保护，
* 将标头设置`X-Frame-Options`为`SAMEORIGIN`来自控制台的响应。

有关[CSRF](https://docs.spring.io/spring-security/reference/6.2/features/exploits/csrf.html)和标头[X-Frame-Options 的](https://docs.spring.io/spring-security/reference/6.2/features/exploits/headers.html#headers-frame-options)更多信息可以在 Spring Security 参考指南中找到。

在简单的设置中，可以使用如下所示的`SecurityFilterChain`内容：

```
@Profile("dev")
@Configuration(proxyBeanMethods = false)
public class DevProfileSecurityConfiguration {
​
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain h2ConsoleSecurityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher(PathRequest.toH2Console());
        http.authorizeHttpRequests(yourCustomAuthorization());
        http.csrf((csrf) -> csrf.disable());
        http.headers((headers) -> headers.frameOptions((frame) -> frame.sameOrigin()));
        return http.build();
    }
​
​
}
```

> H2 控制台仅供开发期间使用。在生产中，禁用 CSRF 保护或允许网站使用框架可能会带来严重的安全风险。
>
> 当控制台的路径已自定义时，`PathRequest.toH2Console()`也会返回正确的请求匹配器。

**9.1.7. 使用 jOOQ**

jOOQ 面向对象查询 ( [jOOQ ) 是](https://www.jooq.org/)[Data Geekery](https://www.datageekery.com/)的一款流行产品，它从数据库生成 Java 代码，并允许您通过其流畅的 API 构建类型安全的 SQL 查询。商业版和开源版都可以与 Spring Boot 一起使用。

**代码生成**

为了使用 jOOQ 类型安全查询，您需要从数据库模式生成 Java 类。[您可以按照jOOQ 用户手册](https://www.jooq.org/doc/3.18.7/manual-single-page/#jooq-in-7-steps-step3)中的说明进行操作。如果您使用`jooq-codegen-maven`插件并且还使用“父 POM”`spring-boot-starter-parent`，则可以安全地省略该插件的`<version>`标签。您还可以使用 Spring Boot 定义的版本变量（例如`h2.version`）来声明插件的数据库依赖项。以下清单显示了一个示例：

```
<plugin>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-codegen-maven</artifactId>
    <executions>
        ...
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
        </dependency>
    </dependencies>
    <configuration>
        <jdbc>
            <driver>org.h2.Driver</driver>
            <url>jdbc:h2:~/yourdatabase</url>
        </jdbc>
        <generator>
            ...
        </generator>
    </configuration>
</plugin>
```

**使用 DSLContext**

jOOQ提供的Fluent API是通过`org.jooq.DSLContext`接口发起的。Spring Boot 自动将 `DSLContext` 配置为 Spring Bean 并将其连接到您应用程序的`DataSource`。要使用`DSLContext`，您可以注入它，如以下示例所示：

```
@Component
public class MyBean {
​
    private final DSLContext create;
​
    public MyBean(DSLContext dslContext) {
        this.create = dslContext;
    }
​
​
}
```

> jOOQ 手册倾向于使用名为`create`的变量来保存`DSLContext`.

然后，您可以使用`DSLContext`来构建查询，如以下示例所示：

```
public List<GregorianCalendar> authorsBornAfter1980() {
    return this.create.selectFrom(AUTHOR)
        .where(AUTHOR.DATE_OF_BIRTH.greaterThan(new GregorianCalendar(1980, 0, 1)))
        .fetch(AUTHOR.DATE_OF_BIRTH);
```

**jOOQ SQL 方言**

除非已配置`spring.jooq.sql-dialect`属性，否则 Spring Boot 会确定用于数据源的 SQL 方言。如果 Spring Boot 无法检测到方言，它会使用`DEFAULT`.

> Spring Boot 只能自动配置开源版本 jOOQ 支持的方言。

**定制jOOQ**

可以通过定义您自己的`DefaultConfigurationCustomizer`bean 来实现更高级的自定义，该 bean 将在创建`org.jooq.Configuration` `@Bean`时调用. 这优先于自动配置应用的任何内容。

如果您想完全控制 jOOQ 配置，您也可以创建自己的`org.jooq.Configuration` `@Bean`。

**9.1.8. 使用 R2DBC**

反应式关系数据库连接 ( [R2DBC](https://r2dbc.io/) ) 项目为关系数据库带来了反应式编程 API。R2DBC的`io.r2dbc.spi.Connection`提供了一种使用非阻塞数据库连接的标准方法。连接是通过使用`ConnectionFactory`提供的，类似于jdbc 中的 `DataSource`。

`ConnectionFactory`配置由 `spring.r2dbc.*`中的外部配置属性控制。例如，您可以在`application.properties` 中声明以下部分：

```
spring.r2dbc.url=r2dbc:postgresql://localhost/test
spring.r2dbc.username=dbuser
spring.r2dbc.password=dbpass
```

> 您不需要指定驱动程序类名，因为 Spring Boot 从 R2DBC 的连接工厂发现中获取驱动程序。
>
> 至少应该提供网址。URL 中指定的信息优先于各个属性，即`name`、`username`和`password`池选项。
>
> “操作方法”部分包括[有关如何初始化数据库的部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-initialization.using-basic-sql-scripts)。

要自定义由`ConnectionFactory` 所创建的连接，即设置您不想（或无法）在中央数据库配置中配置的特定参数，您可以使用`ConnectionFactoryOptionsBuilderCustomizer` `@Bean`. 以下示例显示如何手动覆盖数据库端口，而其余选项则取自应用程序配置：

```
@Configuration(proxyBeanMethods = false)
public class MyR2dbcConfiguration {
​
    @Bean
    public ConnectionFactoryOptionsBuilderCustomizer connectionFactoryPortCustomizer() {
        return (builder) -> builder.option(ConnectionFactoryOptions.PORT, 5432);
    }
​
}
```

以下示例显示如何设置一些 PostgreSQL 连接选项：

```
@Configuration(proxyBeanMethods = false)
public class MyPostgresR2dbcConfiguration {
​
    @Bean
    public ConnectionFactoryOptionsBuilderCustomizer postgresCustomizer() {
        Map<String, String> options = new HashMap<>();
        options.put("lock_timeout", "30s");
        options.put("statement_timeout", "60s");
        return (builder) -> builder.option(PostgresqlConnectionFactoryProvider.OPTIONS, options);
    }
​
}
```

当`ConnectionFactory`bean 可用时，常规 JDBC`DataSource`自动配置就会停止。如果您想保留 JDBC`DataSource`自动配置，并且愿意接受在反应式应用程序中使用阻塞 JDBC API 的风险，请在应用程序中的一个`@Configuration`类添加`@Import(DataSourceAutoConfiguration.class)`以重新启用它。

**嵌入式数据库支持**

与[JDBC 支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.sql.datasource.embedded)类似，Spring Boot 可以自动配置嵌入式数据库以进行反应式使用。您无需提供任何连接 URL。您只需包含对要使用的嵌入式数据库的构建依赖项，如以下示例所示：

```
<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-h2</artifactId>
    <scope>runtime</scope>
</dependency>
```

> 如果您在测试中使用此功能，您可能会注意到，无论您使用的应用程序上下文数量如何，整个测试套件都会重复使用同一个数据库。如果您想确保每个上下文都有一个单独的嵌入式数据库，您应该设置`spring.r2dbc.generate-unique-name`为`true`。

**使用数据库客户端**

`DatabaseClient` bean是自动配置的，您可以将`@Autowire`其直接添加到您自己的 bean 中，如以下示例所示：

```
@Component
public class MyBean {
​
    private final DatabaseClient databaseClient;
​
    public MyBean(DatabaseClient databaseClient) {
        this.databaseClient = databaseClient;
    }
​
    // ...
​
}
```

**Spring Data R2DBC 存储库**

[Spring Data R2DBC](https://spring.io/projects/spring-data-r2dbc)存储库是您可以定义来访问数据的接口。查询是根据您的方法名称自动创建的。例如，`CityRepository`接口可能声明一个`findAllByState(String state)`方法来查找给定州下的所有城市。

对于更复杂的查询，您可以使用 Spring Data 的[`Query`](https://docs.spring.io/spring-data/r2dbc/docs/3.2.0/api/org/springframework/data/r2dbc/repository/Query.html)注释来注释您的方法。

Spring Data 存储库通常从[`Repository`](https://docs.spring.io/spring-data/commons/docs/3.2.0/api/org/springframework/data/repository/Repository.html)或[`CrudRepository`](https://docs.spring.io/spring-data/commons/docs/3.2.0/api/org/springframework/data/repository/CrudRepository.html)接口扩展。如果您使用自动配置，则会[在自动配置包中](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)搜索存储库。

以下示例显示了典型的 Spring Data 存储库接口定义：

```
public interface CityRepository extends Repository<City, Long> {
​
    Mono<City> findByNameAndStateAllIgnoringCase(String name, String state);
​
}
```

我们仅仅触及了 Spring Data R2DBC 的皮毛。有关完整的详细信息，请参阅[Spring Data R2DBC 参考文档](https://docs.spring.io/spring-data/r2dbc/docs/3.2.0/reference/html/)。


# 9.2. 使用 NoSQL 技术

Spring Data 提供了其他项目来帮助您访问各种 NoSQL 技术，包括：

* [Cassandra](https://spring.io/projects/spring-data-cassandra)
* [Couchbase](https://spring.io/projects/spring-data-couchbase)
* [Elasticsearch](https://spring.io/projects/spring-data-elasticsearch)
* [GemFire](https://spring.io/projects/spring-data-gemfire) 或[Geode](https://spring.io/projects/spring-data-geode)
* [LDAP](https://spring.io/projects/spring-data-ldap)
* [MongoDB](https://spring.io/projects/spring-data-mongodb)
* [Neo4J](https://spring.io/projects/spring-data-neo4j)
* [Redis](https://spring.io/projects/spring-data-redis)

其中，Spring Boot 为 Cassandra、Couchbase、Elasticsearch、LDAP、MongoDB、Neo4J 和 Redis 提供自动配置。此外，[Spring Boot for Apache Geode](https://github.com/spring-projects/spring-boot-data-geode)为 Apache Geode提供[自动配置](https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5/#geode-repositories)。您可以使用其他项目，但必须自己配置它们。[请参阅spring.io/projects/spring-data](https://spring.io/projects/spring-data)上的相应参考文档。

Spring Boot 还为 InfluxDB 客户端提供自动配置，但它已被弃用，取而代之的是提供自己的 Spring Boot 集成的[新 InfluxDB Java 客户端。](https://github.com/influxdata/influxdb-client-java)

**9.2.1. Redis**

[Redis](https://redis.io/)是一个缓存、消息代理和功能丰富的键值存储。[Spring Boot 为Lettuce](https://github.com/lettuce-io/lettuce-core/)和[Jedis客户端库以及](https://github.com/xetorthio/jedis/)[Spring Data Redis](https://github.com/spring-projects/spring-data-redis)提供的抽象提供基本的自动配置。

有一个`spring-boot-starter-data-redis`“Starter”可以方便地收集依赖项。默认情况下，它使用[Lettuce](https://github.com/lettuce-io/lettuce-core/)。该启动器可以处理传统应用程序和反应式应用程序。

> 我们还提供了一个`spring-boot-starter-data-redis-reactive`“Starter”，以便与其他存储保持一致并提供反应性支持。

**连接到 Redis**

您可以像注入任何其他 Spring Bean 一样注入自动配置的`RedisConnectionFactory`、`StringRedisTemplate`或普通 `RedisTemplate` 实例。以下清单显示了此类 bean 的示例：

```
@Component
public class MyBean {
​
    private final StringRedisTemplate template;
​
    public MyBean(StringRedisTemplate template) {
        this.template = template;
    }
​
    // ...
​
}
```

默认情况下，实例尝试连接到位于`localhost:6379` 的 Redis 服务器。您可以使用`spring.data.redis.*`属性指定自定义连接详细信息，如以下示例所示：

```
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.username=user
spring.data.redis.password=secret
```

> 您还可以注册任意数量的`LettuceClientConfigurationBuilderCustomizer` Bean 来实现更高级的自定义。 也可以使用`ClientResourcesBuilderCustomizer` 进行`ClientResources`定制。如果你使用Jedis，`JedisClientConfigurationBuilderCustomizer`也是可用的。或者，您可以注册`RedisStandaloneConfiguration`、`RedisSentinelConfiguration`、 或`RedisClusterConfiguration`类型的 bean以完全控制配置。

如果您添加自己的任何自动配置类型的`@Bean`，它将替换默认类型（除了`RedisTemplate` 的情况，当排除是基于 bean 名称`redisTemplate`而不是其类型时）。

默认情况下，如果池连接工厂位于类路径`commons-pool2`上，则会自动配置。

通过设置属性，可以将自动配置`RedisConnectionFactory`配置为使用 SSL 与服务器进行通信，如下例所示：

```
spring.data.redis.ssl.enabled=true
```

[可以在SSL 捆绑包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl)中配置自定义 SSL 信任材料并将其应用到`RedisConnectionFactory`如以下示例：

```
spring.data.redis.ssl.bundle=example
```

**9.2.2. MongoDB**

[MongoDB](https://www.mongodb.com/)是一个开源 NoSQL 文档数据库，它使用类似 JSON 的模式而不是传统的基于表的关系数据。Spring Boot 为使用 MongoDB 提供了多种便利，包括`spring-boot-starter-data-mongodb`“ Starters”和`spring-boot-starter-data-mongodb-reactive`“Starters”。

**连接到 MongoDB 数据库**

要访问 MongoDB 数据库，您可以注入自动配置的`org.springframework.data.mongodb.MongoDatabaseFactory`. 默认情况下，实例尝试连接到位于`mongodb://localhost/test` 的 MongoDB 服务器。以下示例显示如何连接到 MongoDB 数据库：

```
@Component
public class MyBean {
​
    private final MongoDatabaseFactory mongo;
​
    public MyBean(MongoDatabaseFactory mongo) {
        this.mongo = mongo;
    }
​
    // ...
​
}
```

如果您定义了自己的`MongoClient`，它将用于自动配置一个合适的`MongoDatabaseFactory`.

自动配置`MongoClient`是使用`MongoClientSettings`bean 创建的。如果您定义了自己的`MongoClientSettings`，它将不加修改地使用，并且`spring.data.mongodb`属性将被忽略。否则 `MongoClientSettings`将被自动配置并应用属性`spring.data.mongodb`。无论哪种情况，您都可以声明一个或多个`MongoClientSettingsBuilderCustomizer`bean 来微调`MongoClientSettings`配置。每个将按照用于构建`MongoClientSettings` 的顺序被`MongoClientSettings.Builder`调用。

您可以设置`spring.data.mongodb.uri`属性来更改 URL 并配置其他设置，例如*副本集*，如以下示例所示：

```
spring.data.mongodb.uri=mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test
```

或者，您可以使用离散属性指定连接详细信息。例如，您可以在您的 中声明以下设置`application.properties`：

```
spring.data.mongodb.host=mongoserver1.example.com
spring.data.mongodb.port=27017
spring.data.mongodb.additional-hosts[0]=mongoserver2.example.com:23456
spring.data.mongodb.database=test
spring.data.mongodb.username=user
spring.data.mongodb.password=secret
```

通过设置属性，可以将自动配置`MongoClient`配置为使用 SSL 与服务器进行通信，如下例所示：

```
spring.data.mongodb.uri=mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test
spring.data.mongodb.ssl.enabled=true
```

[可以在SSL 捆绑包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl)中配置自定义 SSL 信任材料并将其应用到`MongoClient`以下示例中：

```
spring.data.mongodb.uri=mongodb://user:secret@mongoserver1.example.com:27017,mongoserver2.example.com:23456/test
spring.data.mongodb.ssl.bundle=example
```

> 如果未指定`spring.data.mongodb.port`，则使用默认值`27017`。您可以从前面显示的示例中删除此行。您还可以使用语法将端口指定为主机地址的一部分`host:port`。如果您需要更改条目的端口，则应使用`additional-hosts`格式。
>
> 如果您不使用 Spring Data MongoDB，您可以注入一个`MongoClient`bean 而不是使用`MongoDatabaseFactory`. 如果你想完全控制MongoDB连接的建立，你也可以声明你自己的`MongoDatabaseFactory`或 `MongoClient`bean。
>
> 如果您使用反应式驱动程序，则 SSL 需要 Netty。如果 Netty 可用并且要使用的工厂尚未自定义，则自动配置会自动配置该工厂。

**MongoTemplate**

Spring Data MongoDB 提供了一个 `MongoTemplate` 类，其设计与 Spring 的`JdbcTemplate`非常相似。与`JdbcTemplate`一样，Spring Boot 会自动配置一个 bean 供您注入模板，如下所示：

```
@Component
public class MyBean {
​
    private final MongoTemplate mongoTemplate;
​
    public MyBean(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }
​
    // ...
​
}
```

有关完整详细信息，请参阅[`MongoOperations`Javadoc](https://docs.spring.io/spring-data/mongodb/docs/4.2.0/api/org/springframework/data/mongodb/core/MongoOperations.html)。

**Spring Data MongoDB 存储库**

Spring Data 包括对 MongoDB 的存储库支持。与前面讨论的 JPA 存储库一样，基本原则是根据方法名称自动构造查询。

事实上，Spring Data JPA 和 Spring Data MongoDB 共享相同的公共基础设施。您可以采用前面的 JPA 示例，假设`City`现在是 MongoDB 数据类而不是 JPA `@Entity`，它以相同的方式工作，如以下示例所示：

```
public interface CityRepository extends Repository<City, Long> {
​
    Page<City> findAll(Pageable pageable);
​
    City findByNameAndStateAllIgnoringCase(String name, String state);
​
}
```

通过扫描找到存储库和文档。默认情况下，扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)您可以分别使用`@EnableMongoRepositories`和`@EntityScan`自定义查找存储库和文档的位置。

> 有关 Spring Data MongoDB 的完整详细信息，包括其丰富的对象映射技术，请参阅其[参考文档](https://spring.io/projects/spring-data-mongodb)。

**9.2.3. Neo4j**

[Neo4j](https://neo4j.com/)是一个开源 NoSQL 图形数据库，它使用通过一流关系连接的丰富节点数据模型，比传统 RDBMS 方法更适合互联大数据。Spring Boot 为使用 Neo4j 提供了多种便利，包括`spring-boot-starter-data-neo4j`“Starter”。

**连接到 Neo4j 数据库**

要访问 Neo4j 服务器，您可以注入自动配置的`org.neo4j.driver.Driver`. 默认情况下，实例尝试使用 Bolt 协议连接到 Neo4j 服务器`localhost:7687`。以下示例展示了如何注入 Neo4j `Driver`，该 Neo4j 使您可以访问 `Session`等内容：

```
@Component
public class MyBean {
​
    private final Driver driver;
​
    public MyBean(Driver driver) {
        this.driver = driver;
    }
​
    // ...
​
}
```

您可以使用属性配置驱动程序的各个方面`spring.neo4j.*`。以下示例显示如何配置要使用的 uri 和凭据：

```
spring.neo4j.uri=bolt://my-server:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
```

自动配置的驱动程序是使用`ConfigBuilder`创建的。要微调其配置，请声明一个或多个`ConfigBuilderCustomizer` bean。每个都将按照用于构建驱动程序的`ConfigBuilder` 的顺序被调用。

**Spring Data Neo4j 存储库**

Spring Data 包括对 Neo4j 的存储库支持。有关 Spring Data Neo4j 的完整详细信息，请参阅[参考文档](https://docs.spring.io/spring-data/neo4j/docs/7.2.0/reference/html/)。

与许多其他 Spring Data 模块一样，Spring Data Neo4j 与 Spring Data JPA 共享公共基础设施。您可以采用前面的 JPA 示例并将`City`定义为 Spring Data Neo4j`@Node`而不是 JPA `@Entity`，并且存储库抽象以相同的方式工作，如以下示例所示：

```
public interface CityRepository extends Neo4jRepository<City, Long> {
​
    Optional<City> findOneByNameAndState(String name, String state);
​
}
```

“ Starter `spring-boot-starter-data-neo4j`”支持存储库支持以及事务管理。Spring Boot 使用`Neo4jTemplate`或`ReactiveNeo4jTemplate`beans支持经典和反应式 Neo4j 存储库。当 Project Reactor 在类路径上可用时，响应式样式也会自动配置。

通过扫描找到存储库和实体。默认情况下，扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)您可以分别使用`@EnableNeo4jRepositories`和`@EntityScan`自定义查找存储库和实体的位置。

在使用反应式风格的应用程序中，`ReactiveTransactionManager`不是自动配置的。要启用事务管理，必须在您的配置中定义以下 bean：

```
@Configuration(proxyBeanMethods = false)
public class MyNeo4jConfiguration {
​
    @Bean
    public ReactiveNeo4jTransactionManager reactiveTransactionManager(Driver driver,
            ReactiveDatabaseSelectionProvider databaseNameProvider) {
        return new ReactiveNeo4jTransactionManager(driver, databaseNameProvider);
    }
​
}
```

**9.2.4. Elasticsearch**

[Elasticsearch](https://www.elastic.co/products/elasticsearch)是一个开源、分布式、RESTful 搜索和分析引擎。Spring Boot 为 Elasticsearch 客户端提供基本的自动配置。

Spring Boot 支持多种客户端：

* 官方低级 REST 客户端
* 官方 Java API 客户端
* 由 Spring Data Elasticsearch提供的`ReactiveElasticsearchClient`

Spring Boot 提供了专用的“Starter” ，`spring-boot-starter-data-elasticsearch`.

**使用 REST 客户端连接到 Elasticsearch**

Elasticsearch 提供了两个不同的 REST 客户端，可用于查询集群：`org.elasticsearch.client:elasticsearch-rest-client`模块中的[低级客户端](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low.html)和`co.elastic.clients:elasticsearch-java`模块中的[Java API 客户端](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html)。此外，Spring Boot 还为`org.springframework.data:spring-data-elasticsearch`模块中的响应式客户端提供支持。默认情况下，客户端将定位`localhost:9200`. 您可以使用`spring.elasticsearch.*`属性来进一步调整客户端的配置方式，如以下示例所示：

```
spring.elasticsearch.uris=https://search.example.com:9200
spring.elasticsearch.socket-timeout=10s
spring.elasticsearch.username=user
spring.elasticsearch.password=secret
```

**使用 RestClient 连接到 Elasticsearch**

如果您在类路径上含有`elasticsearch-rest-client`，Spring Boot 将自动配置并注册一个`RestClient`bean。除了前面描述的属性之外，要进行`RestClient`微调，您还可以注册任意数量的 Bean 以实现`RestClientBuilderCustomizer`更高级的自定义。要完全控制客户端的配置，请定义一个`RestClientBuilder`bean。

此外，如果`elasticsearch-rest-client-sniffer`位于类路径上，`Sniffer`会自动配置为自动从正在运行的 Elasticsearch 集群中发现节点并将它们设置在`RestClient`bean 上。您可以进一步调整`Sniffer`配置方式，如以下示例所示：

```
spring.elasticsearch.restclient.sniffer.interval=10m
spring.elasticsearch.restclient.sniffer.delay-after-failure=30s
```

**使用 ElasticsearchClient 连接到 Elasticsearch**

如果`co.elastic.clients:elasticsearch-java`位于类路径上，Spring Boot 将自动配置并注册一个`ElasticsearchClient`bean。

`ElasticsearchClient`使用的传输取决于前面描述的`RestClient`。因此，前面描述的属性可用于配置`ElasticsearchClient`. 此外，您可以定义一个`RestClientOptions`bean 来进一步控制传输的行为。

**使用 ReactiveElasticsearchClient 连接到 Elasticsearch**

[Spring Data Elasticsearch](https://spring.io/projects/spring-data-elasticsearch)用于`ReactiveElasticsearchClient`以反应方式查询 Elasticsearch 实例。如果类路径上有 Spring Data Elasticsearch 和 Reactor，Spring Boot 将自动配置并注册一个`ReactiveElasticsearchClient`.

`ReactiveElasticsearchclient`使用的传输取决于前面描述的`RestClient`。因此，前面描述的属性可用于配置`ReactiveElasticsearchClient`. 此外，您可以定义一个`RestClientOptions`bean 来进一步控制传输的行为。

**使用 Spring Data 连接到 Elasticsearch**

`ElasticsearchClient`要连接到 Elasticsearch，必须定义一个bean，由 Spring Boot 自动配置或由应用程序手动提供（请参阅前面的部分）。完成此配置后， 像任何其他 Spring bean 一样注入`ElasticsearchTemplate`可以 ，如以下示例所示：

```
@Component
public class MyBean {
​
    private final ElasticsearchTemplate template;
​
    public MyBean(ElasticsearchTemplate template) {
        this.template = template;
    }
​
    // ...
​
}
```

在存在`spring-data-elasticsearch`和Reactor的情况下，Spring Boot 还可以自动配置[ReactiveElasticsearchClient](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.nosql.elasticsearch.connecting-using-rest.reactiveclient)和`ReactiveElasticsearchTemplate` beans。它们是其他 REST 客户端的响应式等价物。

**Spring Data Elasticsearch 存储库**

Spring Data 包括对 Elasticsearch 的存储库支持。与前面讨论的 JPA 存储库一样，基本原则是根据方法名称自动为您构建查询。

事实上，Spring Data JPA 和 Spring Data Elasticsearch 共享相同的公共基础设施。您可以采用前面的 JPA 示例，假设`City`现在是 Elasticsearch `@Document`类而不是 JPA `@Entity`，它的工作方式相同。

通过扫描找到存储库和文档。默认情况下，扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)您可以分别使用`@EnableElasticsearchRepositories`和`@EntityScan`自定义查找存储库和文档的位置。

> 有关 Spring Data Elasticsearch 的完整详细信息，请参阅[参考文档](https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/)。

Spring Boot 使用`ElasticsearchRestTemplate`或`ReactiveElasticsearchTemplate`beans支持经典和反应式 Elasticsearch 存储库。如果存在所需的依赖项，这些 bean 很可能是由 Spring Boot 自动配置的。

如果您希望使用自己的模板来支持 Elasticsearch 存储库，则可以添加您自己的`ElasticsearchRestTemplate`或`ElasticsearchOperations` `@Bean`，只要其名称为`"elasticsearchTemplate"`。同样适用于`ReactiveElasticsearchTemplate`和`ReactiveElasticsearchOperations`以及 bean 名称`"reactiveElasticsearchTemplate"`。

您可以选择使用以下属性禁用存储库支持：

```
spring.data.elasticsearch.repositories.enabled=false
```

**9.2.5. Cassandra**

[Cassandra](https://cassandra.apache.org/)是一种开源分布式数据库管理系统，旨在处理跨多个商用服务器的大量数据。Spring Boot 为 Cassandra 提供自动配置，并在其之上由[Spring Data Cassandra](https://github.com/spring-projects/spring-data-cassandra)提供抽象。有一个`spring-boot-starter-data-cassandra`“Starter”可以方便地收集依赖项。

**连接到卡桑德拉**

您可以注入自动配置的实例`CassandraTemplate`或 Cassandra`CqlSession`实例，就像注入任何其他 Spring Bean 一样。这些`spring.cassandra.*`属性可用于自定义连接。通常，您需要使用`keyspace-name`和`contact-points`提供本地数据中心名称，如以下示例所示：

```
spring.cassandra.keyspace-name=mykeyspace
spring.cassandra.contact-points=cassandrahost1:9042,cassandrahost2:9042
spring.cassandra.local-datacenter=datacenter1
```

如果所有接触点的端口都相同，您可以使用快捷方式并仅指定主机名，如下例所示：

```
spring.cassandra.keyspace-name=mykeyspace
spring.cassandra.contact-points=cassandrahost1,cassandrahost2
spring.cassandra.local-datacenter=datacenter1
```

> 这两个示例与默认端口相同`9042`。如果需要配置端口，请使用`spring.cassandra.port`。

通过设置属性，可以将自动配置`CqlSession`配置为使用 SSL 与服务器进行通信，如下例所示：

```
spring.cassandra.keyspace-name=mykeyspace
spring.cassandra.contact-points=cassandrahost1,cassandrahost2
spring.cassandra.local-datacenter=datacenter1
spring.cassandra.ssl.enabled=true
```

[可以在SSL 捆绑包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl)中配置自定义 SSL 信任材料并将其应用到`CqlSession`以下示例中：

```
spring.cassandra.keyspace-name=mykeyspace
spring.cassandra.contact-points=cassandrahost1,cassandrahost2
spring.cassandra.local-datacenter=datacenter1
spring.cassandra.ssl.bundle=example
```

> Cassandra 驱动程序有自己的配置基础架构，可在类路径的根目录加载 `application.conf`。
>
> Spring Boot 默认情况下不会查找此类文件，但可以使用`spring.cassandra.config`加载该文件。如果某个属性同时存在于 `spring.cassandra.*` 和配置文件中，则 `spring.cassandra.*`中的值优先。
>
> 对于更高级的驱动程序自定义，您可以注册任意数量的实现`DriverConfigLoaderBuilderCustomizer` 的 bean。可以使用 `CqlSessionBuilderCustomizer` 类型的 bean 来自定义 CqlSession。
>
> 如果您用来`CqlSessionBuilder`创建多个`CqlSession`bean，请记住构建器是可变的，因此请确保为每个会话注入一个新的副本。

以下代码清单显示了如何注入 Cassandra bean：

```
@Component
public class MyBean {
​
    private final CassandraTemplate template;
​
    public MyBean(CassandraTemplate template) {
        this.template = template;
    }
​
    // ...
​
}
```

如果您添加自己的`CassandraTemplate`类型的`@Bean`，它将替换默认值。

**Spring Data Cassandra 存储库**

Spring Data 包括对 Cassandra 的基本存储库支持。目前，这比前面讨论的 JPA 存储库更加有限，并且需要`@Query`带注释的查找器方法。

通过扫描找到存储库和实体。默认情况下，扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)您可以分别使用`@EnableCassandraRepositories`和`@EntityScan`自定义查找存储库和实体的位置。

> 有关 Spring Data Cassandra 的完整详细信息，请参阅[参考文档](https://docs.spring.io/spring-data/cassandra/docs/)。

**9.2.6. Couchbase**

[Couchbase](https://www.couchbase.com/)是一个开源、分布式、多模型、面向文档的 NoSQL 数据库，针对交互式应用程序进行了优化。Spring Boot 为 Couchbase 提供自动配置，并在其之上由[Spring Data Couchbase](https://github.com/spring-projects/spring-data-couchbase)提供抽象。有“ `spring-boot-starter-data-couchbase`Starters `spring-boot-starter-data-couchbase-reactive`”可以方便地收集依赖项。

**连接到 Couchbase**

您可以通过添加 Couchbase SDK 和一些配置来获得`Cluster`。这些`spring.couchbase.*`属性可用于自定义连接。通常，您需要提供[连接字符串](https://github.com/couchbaselabs/sdk-rfcs/blob/master/rfc/0011-connection-string.md)、用户名和密码，如以下示例所示：

```
spring.couchbase.connection-string=couchbase://192.168.1.123
spring.couchbase.username=user
spring.couchbase.password=secret
```

还可以自定义一些设置`ClusterEnvironment`。例如，以下配置更改打开新`Bucket`的超时时间并通过引用已配置的[SSL 捆绑包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl)启用 SSL 支持：

```
spring.couchbase.env.timeouts.connect=3s
spring.couchbase.env.ssl.bundle=example
```

> 检查`spring.couchbase.env.*`属性以获取更多详细信息。为了更好地控制，可以使用一种或多种`ClusterEnvironmentBuilderCustomizer`bean。

**Spring Data Couchbase 存储库**

Spring Data 包括对 Couchbase 的存储库支持。

通过扫描找到存储库和文档。默认情况下，扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)您可以分别使用`@EnableCouchbaseRepositories`和`@EntityScan`自定义查找存储库和文档的位置。

有关 Spring Data Couchbase 的完整详细信息，请参阅[参考文档](https://docs.spring.io/spring-data/couchbase/docs/5.2.0/reference/html/)。

您可以像注入任何其他 Spring Bean 一样注入自动配置的`CouchbaseTemplate`实例，前提是有可用的`CouchbaseClientFactory` Bean。当 `Cluster`可用时（如上所述）并且已指定存储桶名称，就会发生这种情况：

```
spring.data.couchbase.bucket-name=my-bucket
```

以下示例展示了如何注入`CouchbaseTemplate`Bean：

```
@Component
public class MyBean {
​
    private final CouchbaseTemplate template;
​
    public MyBean(CouchbaseTemplate template) {
        this.template = template;
    }
​
    // ...
​
}
```

您可以在自己的配置中定义一些 bean，以覆盖自动配置提供的那些 bean：

* 名字为`couchbaseMappingContext`的`CouchbaseMappingContext` `@Bean`.
* 名字为`couchbaseCustomConversions`的`CustomConversions` `@Bean`
* 名字为`couchbaseTemplate`的`CouchbaseTemplate` `@Bean`

为了避免在您自己的配置中硬编码这些名称，您可以重用Spring Data Couchbase 提供的`BeanNames`名称。例如，您可以自定义要使用的转换器，如下所示：

```
@Configuration(proxyBeanMethods = false)
public class MyCouchbaseConfiguration {
​
    @Bean(BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
    public CouchbaseCustomConversions myCustomConversions() {
        return new CouchbaseCustomConversions(Arrays.asList(new MyConverter()));
    }
​
}
```

**9.2.7. LDAP**

[LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)（轻量级目录访问协议）是一种开放的、供应商中立的行业标准应用协议，用于通过 IP 网络访问和维护分布式目录信息服务。Spring Boot 为任何兼容的 LDAP 服务器提供自动配置，并支持来自[UnboundID 的](https://ldap.com/unboundid-ldap-sdk-for-java/)嵌入式内存 LDAP 服务器。

[LDAP 抽象由Spring Data LDAP](https://github.com/spring-projects/spring-data-ldap)提供。有一个`spring-boot-starter-data-ldap`“Starter”可以方便地收集依赖项。

**连接到 LDAP 服务器**

要连接到 LDAP 服务器，请确保声明了`spring-boot-starter-data-ldap`“Starter”或者`spring-ldap-core`的依赖项，然后在 application.properties 中声明服务器的 URL，如以下示例所示：

```
spring.ldap.urls=ldap://myserver:1235
spring.ldap.username=admin
spring.ldap.password=secret
```

如果需要自定义连接设置，可以使用`spring.ldap.base`和`spring.ldap.base-environment`属性。

`LdapContextSource`是根据这些设置自动配置的。如果`DirContextAuthenticationStrategy`bean 可用，它将与自动配置的`LdapContextSource`关联. 如果您需要自定义它，例如使用 `PooledContextSource`，您仍然可以注入自动配置的`LdapContextSource`. 确保将您的自定义`ContextSource`标记为`@Primary`，以便自动配置`LdapTemplate`使用它。

**Spring Data LDAP 存储库**

Spring Data 包括对 LDAP 的存储库支持。

通过扫描找到存储库和文档。默认情况下，扫描[自动配置包。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.auto-configuration.packages)您可以分别使用`@EnableLdapRepositories`和`@EntityScan`自定义查找存储库和文档的位置。

有关 Spring Data LDAP 的完整详细信息，请参阅[参考文档](https://docs.spring.io/spring-data/ldap/docs/1.0.x/reference/html/)。

您还可以像注入任何其他 Spring Bean 一样注入自动配置的`LdapTemplate`实例，如以下示例所示：

```
@Component
public class MyBean {
​
    private final LdapTemplate template;
​
    public MyBean(LdapTemplate template) {
        this.template = template;
    }
​
    // ...
​
}
```

**嵌入式内存 LDAP 服务器**

[出于测试目的，Spring Boot 支持从UnboundID](https://ldap.com/unboundid-ldap-sdk-for-java/)自动配置内存中 LDAP 服务器。要配置服务器，请添加依赖项`com.unboundid:unboundid-ldapsdk`并声明`spring.ldap.embedded.base-dn`属性，如下所示：

```
spring.ldap.embedded.base-dn=dc=spring,dc=io
```

可以定义多个基本 dn 值，但是，由于可分辨名称通常包含逗号，因此必须使用正确的符号来定义它们。在 yaml 文件中，您可以使用 yaml 列表表示法。在属性文件中，您必须包含索引作为属性名称的一部分：

```
spring.ldap.embedded.base-dn[0]=dc=spring,dc=io spring.ldap.embedded.base-dn[1]=dc=vmware,dc=com
```

默认情况下，服务器在随机端口上启动并触发常规 LDAP 支持。无需指定`spring.ldap.urls`属性。

如果类路径上有一个`schema.ldif`文件，它将用于初始化服务器。如果要从不同的资源加载初始化脚本，也可以使用`spring.ldap.embedded.ldif`属性。

默认情况下，使用标准架构来验证`LDIF`文件。您可以通过设置`spring.ldap.embedded.validation.enabled`属性来完全关闭验证。如果您有自定义属性，则可以用来`spring.ldap.embedded.validation.schema`定义自定义属性类型或对象类。

**9.2.8. InfluxDB**

> InfluxDB 的自动配置已被弃用，并计划在 Spring Boot 3.4 中删除，以支持提供自己的 Spring Boot 集成的 [新 InfluxDB Java 客户端。](https://github.com/influxdata/influxdb-client-java)

[InfluxDB](https://www.influxdata.com/)是一款开源时间序列数据库，专为操作监控、应用程序指标、物联网传感器数据和实时分析等领域的时间序列数据的快速、高可用性存储和检索而优化。

**连接到 InfluxDB**

Spring Boot 会自动配置一个`InfluxDB`实例，前提是`influxdb-java`客户端位于类路径上并且数据库的 URL 使用`spring.influx.url`设置.

如果连接到 InfluxDB 需要用户名和密码，您可以相应地设置`spring.influx.user`和`spring.influx.password`属性。

InfluxDB 依赖于 OkHttp。如果您需要调整 http 客户端`InfluxDB`在幕后使用，您可以注册一个`InfluxDbOkHttpClientBuilderProvider`bean。

如果您需要对配置进行更多控制，请考虑注册`InfluxDbCustomizer`bean。


# 9.3. 接下来读什么

您现在应该了解如何将 Spring Boot 与各种数据技术结合使用。从这里，您可以了解 Spring Boot 对各种[消息传递技术](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging)的支持以及如何在应用程序中启用它们。

<br>


# 10. 消息

### &#x20;10. 消息传递

Spring 框架为与消息系统集成提供了广泛的支持，从 JMS API 的简化使用`JmsTemplate`到异步接收消息的完整基础设施。Spring AMQP 为高级消息队列协议提供了类似的功能集。Spring Boot 还为RabbitMQ和`RabbitTemplate`提供自动配置选项。Spring WebSocket 本身包含对 STOMP 消息传递的支持，而 Spring Boot 通过启动器和少量自动配置对此提供支持。Spring Boot 还支持 Apache Kafka 和 Apache Pulsar。

####

####

####

####

####

####

####

####

<br>


# 10.1. JMS

`jakarta.jms.ConnectionFactory`接口提供了创建与 JMS 代理交互的`jakarta.jms.Connection`标准方法。尽管 Spring 需要使用 JMS`ConnectionFactory`，但您通常不需要自己直接使用它，而是可以依赖更高级别的消息传递抽象。（有关详细信息，请参阅 Spring 框架参考文档的[相关部分。）Spring Boot 还会自动配置发送和接收消息所需的基础设施。](https://docs.spring.io/spring-framework/reference/6.1/integration/jms.html)

**10.1.1. ActiveMQ“经典”支持**

当[ActiveMQ“Classic”](https://activemq.apache.org/components/classic)在类路径上可用时，Spring Boot 可以配置一个`ConnectionFactory`.

> 如果您使用`spring-boot-starter-activemq`，则会提供连接到 ActiveMQ“经典”实例所需的依赖项，以及与 JMS 集成的 Spring 基础结构。

ActiveMQ“经典”配置由`spring.activemq.*`. 默认情况下，ActiveMQ“经典”自动配置为使用[TCP 传输](https://activemq.apache.org/tcp-transport-reference)，默认连接到`tcp://localhost:61616`。以下示例显示如何更改默认代理 URL：

```
spring.activemq.broker-url=tcp://192.168.1.210:9876
spring.activemq.user=admin
spring.activemq.password=secret
```

默认情况下，`CachingConnectionFactory`使用合理的设置包装原生的`ConnectionFactory`，您可以通过`spring.jms.*`中的外部配置属性来控制这些设置`spring.jms.*`：

```
spring.jms.cache.session-cache-size=5
```

如果您更愿意使用本机池，可以通过添加依赖项`org.messaginghub:pooled-jms`并进行相应配置来实现`JmsPoolConnectionFactory`，如以下示例所示：

```
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=50
```

> 请参阅[`ActiveMQProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java)参考资料 了解更多支持的选项。您还可以注册任意数量的`ActiveMQConnectionFactoryCustomizer` Bean 来实现更高级的自定义。

默认情况下，ActiveMQ“经典”会创建一个目标（如果目标尚不存在），以便根据提供的名称解析目标。

**10.1.2. ActiveMQ Artemis 支持**

当 Spring Boot 检测到[ActiveMQ Artemis](https://activemq.apache.org/components/artemis/)在类路径上可用时，它可以自动配置 `ConnectionFactory` 。如果代理存在，则会自动启动和配置嵌入式代理（除非已显式设置模式属性）。支持的模式是`embedded`（明确需要嵌入式代理，并且如果代理在类路径上不可用，则会发生错误）和`native`（使用`netty`传输协议连接到代理）。配置后者后，Spring Boot 将配置一个`ConnectionFactory`连接到使用默认设置在本地计算机上运行的代理 。

> 如果您使用`spring-boot-starter-artemis`，则会提供连接到现有 ActiveMQ Artemis 实例所需的依赖项，以及与 JMS 集成的 Spring 基础设施。通过添加`org.apache.activemq:artemis-jakarta-server`到您的应用程序，您可以使用嵌入模式。

ActiveMQ Artemis 配置由`spring.artemis.*`. 例如，您可以在`application.properties` 中声明以下部分：

```
spring.artemis.mode=native
spring.artemis.broker-url=tcp://192.168.1.210:9876
spring.artemis.user=admin
spring.artemis.password=secret
```

嵌入代理时，您可以选择是否要启用持久性并列出应可用的目标。这些可以指定为逗号分隔的列表，以使用默认选项创建它们，或者您可以定义 `org.apache.activemq.artemis.jms.server.config.JMSQueueConfiguration`或 `org.apache.activemq.artemis.jms.server.config.TopicConfiguration` 类型的 bean，分别用于高级队列和主题配置。

默认情况下，`CachingConnectionFactory`使用合理的设置包装原生`ConnectionFactory`，您可以通过以下中的外部配置属性来控制这些设置`spring.jms.*`：

```
spring.jms.cache.session-cache-size=5
```

如果您更愿意使用本机池，可以通过添加依赖项`org.messaginghub:pooled-jms`并进行相应配置来实现`JmsPoolConnectionFactory`，如以下示例所示：

```
spring.artemis.pool.enabled=true
spring.artemis.pool.max-connections=50
```

请参阅[`ArtemisProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java)参考资料 了解更多支持的选项。

不涉及 JNDI 查找，并且使用ActiveMQ Artemis 配置中的`name`属性或通过配置提供的名称根据其名称解析目标。

**10.1.3. 使用 JNDI ConnectionFactory**

如果您在应用程序服务器中运行应用程序，Spring Boot 会尝试`ConnectionFactory`使用 JNDI 来查找 JMS。默认情况下，会检查`java:/JmsXA`和`java:/XAConnectionFactory`位置。如果需要指定替代位置，则可以使用`spring.jms.jndi-name`属性，如下例所示：

```
spring.jms.jndi-name=java:/MyConnectionFactory
```

**10.1.4. 发送消息**

Spring`JmsTemplate`是自动配置的，您可以将其直接自动装配到您自己的 bean 中，如以下示例所示：

```
@Component
public class MyBean {
​
    private final JmsTemplate jmsTemplate;
​
    public MyBean(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }
​
    // ...
​
}
```

> [`JmsMessagingTemplate`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/jms/core/JmsMessagingTemplate.html)可以用类似的方式注射。如果定义了一个`DestinationResolver`或一个`MessageConverter`bean，它会自动关联到自动配置的`JmsTemplate`.

**10.1.5. 接收消息**

当存在 JMS 基础设施时，可以对任何 Bean 进行注释`@JmsListener`以创建侦听器端点。如果`JmsListenerContainerFactory`未定义，则自动配置默认值。如果定义了 `DestinationResolver`、 `MessageConverter`或 `jakarta.jms.ExceptionListener`beans，它们会自动与默认工厂关联。

默认情况下，默认工厂是事务性的。如果您在存在 `JtaTransactionManager`的基础架构中运行，则默认情况下它会与侦听器容器关联。如果没有，则启用`sessionTransacted`标志。在后一种情况下，您可以通过在侦听器方法（或其委托）添加`@Transactional`将本地数据存储事务与传入消息的处理关联起来。这可确保本地事务完成后传入的消息得到确认。这还包括发送在同一 JMS 会话上执行的响应消息。

以下组件在`someQueue`目标上创建侦听器端点：

```
@Component
public class MyBean {

    @JmsListener(destination = "someQueue")
    public void processMessage(String content) {
        // ...
    }

}
```

有关详细信息， 请参阅[`@EnableJms`的Javadoc 。](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/jms/annotation/EnableJms.html)

如果您需要创建更多`JmsListenerContainerFactory`实例或者想要覆盖默认值，Spring Boot 提供了一个`DefaultJmsListenerContainerFactoryConfigurer`实例，您可以使用它来初始化一个`DefaultJmsListenerContainerFactory`与自动配置的实例相同的设置。

例如，以下示例公开了另一个使用特定 `MessageConverter`的工厂：

```
@Configuration(proxyBeanMethods = false)
public class MyJmsConfiguration {

    @Bean
    public DefaultJmsListenerContainerFactory myFactory(DefaultJmsListenerContainerFactoryConfigurer configurer) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        ConnectionFactory connectionFactory = getCustomConnectionFactory();
        configurer.configure(factory, connectionFactory);
        factory.setMessageConverter(new MyMessageConverter());
        return factory;
    }

    private ConnectionFactory getCustomConnectionFactory() {
        return ...
    }

}
```

然后您可以在任何带`@JmsListener`注释的方法中使用工厂，如下所示：

```
@Component
public class MyBean {

    @JmsListener(destination = "someQueue", containerFactory = "myFactory")
    public void processMessage(String content) {
        // ...
    }

}
```


# 10.2. AMQP

高级消息队列协议 (AMQP) 是面向消息中间件的平台中立、线路级协议。Spring AMQP 项目将核心 Spring 概念应用于基于 AMQP 的消息传递解决方案的开发。Spring Boot 为通过 RabbitMQ 使用 AMQP 提供了多种便利，包括`spring-boot-starter-amqp`“Starter”。

**10.2.1. RabbitMQ 支持**

[RabbitMQ](https://www.rabbitmq.com/)是一个基于 AMQP 协议的轻量级、可靠、可扩展、可移植的消息代理。Spring使用RabbitMQ通过AMQP协议进行通信。

RabbitMQ 配置为`spring.rabbitmq.*`. 例如，您可以在`application.properties` 中声明以下部分：

```
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=secret
```

或者，您可以使用以下`addresses`属性配置相同的连接：

```
spring.rabbitmq.addresses=amqp://admin:secret@localhost
```

> 当以这种方式指定地址时，`host`和`port`属性将被忽略。如果地址使用该`amqps`协议，则会自动启用 SSL 支持。

请参阅[`RabbitProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitProperties.java) 参考资料了解更多受支持的基于属性的配置选项。要配置Spring AMQP 使用的RabbitMQ `ConnectionFactory`的较低级别详细信息，请定义一个`ConnectionFactoryCustomizer`bean。

如果上下文中存在`ConnectionNameStrategy` bean，它将自动用于命名由自动配置的`CachingConnectionFactory`创建的连接.

要对`RabbitTemplate` 进行应用程序范围的附加定制，请使用`RabbitTemplateCustomizer`bean。

> 有关更多详细信息 ， 请参阅[了解 RabbitMQ 使用的协议 AMQP 。](https://spring.io/blog/2010/06/14/understanding-amqp-the-protocol-used-by-rabbitmq/)

**10.2.2. 发送消息**

Spring 的`AmqpTemplate`和`AmqpAdmin`是自动配置的，您可以将它们直接自动装配到您自己的 bean 中，如以下示例所示：

```
@Component
public class MyBean {

    private final AmqpAdmin amqpAdmin;

    private final AmqpTemplate amqpTemplate;

    public MyBean(AmqpAdmin amqpAdmin, AmqpTemplate amqpTemplate) {
        this.amqpAdmin = amqpAdmin;
        this.amqpTemplate = amqpTemplate;
    }

    // ...

}
```

> [`RabbitMessagingTemplate`](https://docs.spring.io/spring-amqp/docs/3.1.0/api/org/springframework/amqp/rabbit/core/RabbitMessagingTemplate.html)可以用类似的方式注入。如果`MessageConverter`定义了一个 bean，它会自动关联到自动配置的`AmqpTemplate`.

如有必要，任何定义为 bean 的 `org.springframework.amqp.core.Queue` 都会自动用于在 RabbitMQ 实例上声明相应的队列。

要重试操作，您可以启用`AmqpTemplate`的重试（例如，在代理连接丢失的情况下）：

```
spring.rabbitmq.template.retry.enabled=true
spring.rabbitmq.template.retry.initial-interval=2s
```

默认情况下禁用重试。您还可以通过声明`RabbitRetryTemplateCustomizer`bean 以编程方式自定义`RetryTemplate`。

如果您需要创建更多`RabbitTemplate`实例或者想要覆盖默认值，Spring Boot 提供了一个`RabbitTemplateConfigurer`bean，您可以使用该 bean 来初始化`RabbitTemplate`与自动配置所使用的工厂相同的设置。

**10.2.3. 向流发送消息**

要将消息发送到特定流，请指定流的名称，如以下示例所示：

```
spring.rabbitmq.stream.name=my-stream
```

如果定义了`MessageConverter`、`StreamMessageConverter`或`ProducerCustomizer`bean，它会自动关联到自动配置的`RabbitStreamTemplate`.

如果您需要创建更多`RabbitStreamTemplate`实例或者想要覆盖默认值，Spring Boot 提供了一个`RabbitStreamTemplateConfigurer`bean，您可以使用该 bean 来初始化`RabbitStreamTemplate`与自动配置所使用的工厂相同的设置。

**10.2.4. 接收消息**

当 Rabbit 基础设施存在时，任何 bean 都可以被注释`@RabbitListener`以创建侦听器端点。如果`RabbitListenerContainerFactory`未定义，则会自动配置默认值`SimpleRabbitListenerContainerFactory`，您可以使用`spring.rabbitmq.listener.type`属性切换到直接容器。如果定义了一个`MessageConverter`或一个`MessageRecoverer`bean，它会自动与默认工厂关联。

以下示例组件在`someQueue`队列上创建侦听器端点：

```
@Component
public class MyBean {

    @RabbitListener(queues = "someQueue")
    public void processMessage(String content) {
        // ...
    }

}
```

> 有关详细信息， 请参阅[`@EnableRabbit`Javadoc ](https://docs.spring.io/spring-amqp/docs/3.1.0/api/org/springframework/amqp/rabbit/annotation/EnableRabbit.html)。

如果您需要创建更多`RabbitListenerContainerFactory`实例或者想要覆盖默认值，Spring Boot 提供了 `SimpleRabbitListenerContainerFactoryConfigurer`和 `DirectRabbitListenerContainerFactoryConfigurer`，您可以使用它们来初始化 `SimpleRabbitListenerContainerFactory`和 `DirectRabbitListenerContainerFactory`，其设置与自动配置所使用的工厂相同。

> 您选择哪种容器类型并不重要。这两个 bean 通过自动配置公开。

例如，以下配置类公开了另一个使用特定`MessageConverter`的工厂：

```
@Configuration(proxyBeanMethods = false)
public class MyRabbitConfiguration {

    @Bean
    public SimpleRabbitListenerContainerFactory myFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        ConnectionFactory connectionFactory = getCustomConnectionFactory();
        configurer.configure(factory, connectionFactory);
        factory.setMessageConverter(new MyMessageConverter());
        return factory;
    }

    private ConnectionFactory getCustomConnectionFactory() {
        return ...
    }

}
```

然后你可以在任何带`@RabbitListener`注释的方法中使用工厂，如下所示：

```
@Component
public class MyBean {

    @RabbitListener(queues = "someQueue", containerFactory = "myFactory")
    public void processMessage(String content) {
        // ...
    }

}
```

您可以启用重试来处理侦听器引发异常的情况。默认情况下，使用`RejectAndDontRequeueRecoverer` ，但您可以定义自己的`MessageRecoverer`。当重试次数用尽时，消息将被拒绝并被丢弃或路由到死信交换（如果代理配置为这样做）。默认情况下，重试被禁用。您还可以通过声明`RabbitRetryTemplateCustomizer`bean 以编程方式自定义`RetryTemplate`。

> 默认情况下，如果禁用重试并且侦听器引发异常，则将无限期地重试传递。您可以通过两种方式修改此行为：将`defaultRequeueRejected`属性设置为`false`以便尝试零次重新传递，或者抛出`AmqpRejectAndDontRequeueException`来表示应拒绝消息。后者是启用重试并且达到最大传递尝试次数时使用的机制。


# 10.3. Apache Kafka 支持

[Apache Kafka](https://kafka.apache.org/)通过提供`spring-kafka`项目的自动配置来支持。

Kafka 配置为`spring.kafka.*`. 例如，您可以在 `application.properties`中声明以下部分：

```
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=myGroup
```

> 要在启动时创建topic，请添加类型为 `NewTopic`的bean 。如果主题已经存在，则忽略该 bean。

请参阅[`KafkaProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java)参考资料 了解更多支持的选项。

**10.3.1. 发送消息**

Spring`KafkaTemplate`是自动配置的，您可以直接在自己的 bean 中自动装配它，如以下示例所示：

```
@Component
public class MyBean {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public MyBean(KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    // ...

}
```

如果定义了`spring.kafka.producer.transaction-id-prefix`属性，则会自动配置 `KafkaTransactionManager`。此外，如果定义了一个`RecordMessageConverter` bean，它会自动关联到自动配置的`KafkaTemplate`.

**10.3.2. 接收消息**

当 Apache Kafka 基础设施存在时，可以对任何 bean 进行`@KafkaListener`注释以创建侦听器端点。如果未定义`KafkaListenerContainerFactory`，则会使用 `spring.kafka.listener.*`中定义的键自动配置默认值。

以下组件在`someTopic`主题上创建侦听器端点：

```
@Component
public class MyBean {

    @KafkaListener(topics = "someTopic")
    public void processMessage(String content) {
        // ...
    }

}
```

如果`KafkaTransactionManager`定义了一个bean，它会自动关联到容器工厂。类似地，如果定义了`RecordFilterStrategy`、`CommonErrorHandler`、`AfterRollbackProcessor`或`ConsumerAwareRebalanceListener`bean，它会自动关联到默认工厂。

根据侦听器类型，`RecordMessageConverter`或`BatchMessageConverter`bean 会与默认工厂关联。如果批处理侦听器仅存在一个 `RecordMessageConverter` bean，则它将包装在`BatchMessageConverter`.

> 自定义的 `ChainedKafkaTransactionManager`必须标记 `@Primary`，因为它通常引用自动配置的`KafkaTransactionManager`bean。

**10.3.3. 卡夫卡流**

Spring for Apache Kafka 提供了一个工厂 bean 来创建`StreamsBuilder`对象并管理其流的生命周期。只要所需的bean`kafka-streams`位于类路径中，并且通过`@EnableKafkaStreams`注释启用了 Kafka Streams，Spring Boot 就会自动配置所需的`KafkaStreamsConfiguration` bean。

启用 Kafka Streams 意味着必须设置应用程序 ID 和引导服务器。前者可以使用 进行配置，如果不设置则`spring.kafka.streams.application-id`默认为`spring.application.name`。后者可以全局设置或仅针对流专门覆盖。

使用专用属性可以使用几个附加属性；可以使用命名空间`spring.kafka.streams.properties`设置其他任意 Kafka 属性。另请参阅[其他 Kafka 属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.kafka.additional-properties)以获取更多信息。

要使用工厂 bean，请按以下示例所示连接`StreamsBuilder`到您的`@Bean` ：

```
@Configuration(proxyBeanMethods = false)
@EnableKafkaStreams
public class MyKafkaStreamsConfiguration {

    @Bean
    public KStream<Integer, String> kStream(StreamsBuilder streamsBuilder) {
        KStream<Integer, String> stream = streamsBuilder.stream("ks1In");
        stream.map(this::uppercaseValue).to("ks1Out", Produced.with(Serdes.Integer(), new JsonSerde<>()));
        return stream;
    }

    private KeyValue<Integer, String> uppercaseValue(Integer key, String value) {
        return new KeyValue<>(key, value.toUpperCase());
    }

}
```

默认情况下，对象管理的流`StreamBuilder`会自动启动。您可以使用`spring.kafka.streams.auto-startup`属性自定义此行为。

**10.3.4. 其他 Kafka 属性**

自动配置支持的属性显示在附录的[“集成属性”部分中。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.application-properties.integration)请注意，在大多数情况下，这些属性（连字符或驼峰命名法）直接映射到 Apache Kafka 点分属性。有关详细信息，请参阅 Apache Kafka 文档。

名称中不包含客户端类型（`producer`、`consumer`、`admin`或`streams`）的属性被视为通用属性并适用于所有客户端。如果需要，可以为一种或多种客户端类型覆盖大多数常见属性。

Apache Kafka 将属性的重要性指定为“高”、“中”或“低”。Spring Boot 自动配置支持所有高重要性属性、一些选定的中和低属性以及任何没有默认值的属性。

只有 Kafka 支持的属性的子集可以直接通过`KafkaProperties`类获得。如果您希望使用不直接支持的其他属性来配置各个客户端类型，请使用以下属性：

```
spring.kafka.properties[prop.one]=first
spring.kafka.admin.properties[prop.two]=second
spring.kafka.consumer.properties[prop.three]=third
spring.kafka.producer.properties[prop.four]=fourth
spring.kafka.streams.properties[prop.five]=fifth
```

这将公共`prop.one`Kafka 属性设置为`first`（适用于生产者、消费者、管理员和流），将`prop.two`管理属性设置为`second`，将`prop.three`消费者属性设置为`third`，将`prop.four`生产者属性设置为`fourth`，将`prop.five`流属性设置为`fifth`。

您还可以按如下方式配置Spring Kafka `JsonDeserializer`：

```
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties[spring.json.value.default.type]=com.example.Invoice
spring.kafka.consumer.properties[spring.json.trusted.packages]=com.example.main,com.example.another
```

同样，您可以禁用在标头中发送类型信息的默认`JsonSerializer`行为：

```
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
spring.kafka.producer.properties[spring.json.add.type.headers]=false
```

> 以这种方式设置的属性会覆盖 Spring Boot 明确支持的任何配置项。

**10.3.5. 使用嵌入式 Kafka 进行测试**

Spring for Apache Kafka 提供了一种使用嵌入式 Apache Kafka 代理测试项目的便捷方法。要使用此功能，请使用`spring-kafka-test`模块`@EmbeddedKafka`注释测试类。有关更多信息，请参阅 Spring for Apache Kafka[参考手册](https://docs.spring.io/spring-kafka/docs/3.1.0/reference/testing.html#ekb)。

要使 Spring Boot 自动配置与上述嵌入式 Apache Kafka 代理配合使用，您需要将嵌入式代理地址的系统属性（由 填充）重新映射`EmbeddedKafkaBroker`到 Apache Kafka 的 Spring Boot 配置属性。有几种方法可以做到这一点：

* 提供一个系统属性以将嵌入式代理地址映射到`spring.kafka.bootstrap-servers`测试类中：

  ```
  static {
      System.setProperty(EmbeddedKafkaBroker.BROKER_LIST_PROPERTY, "spring.kafka.bootstrap-servers");
  }
  ```
* 在注释上配置属性名称`@EmbeddedKafka`：

  ```
  @SpringBootTest
  @EmbeddedKafka(topics = "someTopic", bootstrapServersProperty = "spring.kafka.bootstrap-servers")
  class MyTest {
  ​
      // ...
  ​
  }
  ```
* 在配置属性中使用占位符：

  ```
  spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}
  ```


# 10.4. Apache Pulsar 支持

[Apache Pulsar通过提供](https://pulsar.apache.org/)[Spring for Apache Pulsar](https://docs.spring.io/spring-pulsar/docs/1.0.0/reference/)项目的自动配置来支持。

当`org.springframework.pulsar:spring-pulsar`位于类路径上时，Spring Boot 将自动配置并注册经典（强制）Spring for Apache Pulsar 组件。当`org.springframework.pulsar:spring-pulsar-reactive`位于类路径上时，它将对反应式组件执行相同的操作。

有`spring-boot-starter-pulsar`和`spring-boot-starter-pulsar-reactive`“Starters” 分别用于方便地收集命令式和反应式使用的依赖项。

**10.4.1. 连接到 Pulsar**

当您使用 Pulsar 启动器时，Spring Boot 将自动配置并注册一个`PulsarClient`bean。

默认情况下，应用程序尝试连接到位于 的本地 Pulsar 实例`pulsar://localhost:6650`。这可以通过将`spring.pulsar.client.service-url`属性设置为不同的值来调整。

> 该值必须是有效的[Pulsar 协议](https://pulsar.apache.org/docs/client-libraries-java/#connection-urls)URL

您可以通过指定任何带`spring.pulsar.client.*`前缀的应用程序属性来配置客户端。

如果您需要对配置进行更多控制，请考虑注册一个或多个`PulsarClientBuilderCustomizer`bean。

**验证**

要连接到需要身份验证的 Pulsar 集群，您需要通过设置`pluginClassName`和插件所需的任何参数来指定要使用的身份验证插件。您可以将参数设置为参数名称到参数值的映射。以下示例展示了如何配置`AuthenticationOAuth2`插件。

```
spring.pulsar.client.authentication.plugin-class-name=org.apache.pulsar.client.impl.auth.oauth2.AuthenticationOAuth2
spring.pulsar.client.authentication.param[issuerUrl]=https://auth.server.cloud/
spring.pulsar.client.authentication.param[privateKey]=file:///Users/some-key.json
spring.pulsar.client.authentication.param.audience=urn:sn:acme:dev:my-instance
```

> 您需要确保`spring.pulsar.client.authentication.param.*`下面定义的名称与您的身份验证插件所期望的名称完全匹配（通常是驼峰式大小写）。Spring Boot 不会尝试对这些条目进行任何类型的宽松绑定。
>
> 例如，如果您想为`AuthenticationOAuth2`auth 插件配置颁发者 url，则必须使用`spring.pulsar.client.authentication.param.issuerUrl`. 如果您使用其他形式，例如`issuerurl`或`issuer-url`，该设置将不会应用于插件。

**SSL协议**

默认情况下，Pulsar 客户端以纯文本方式与 Pulsar 服务进行通信。您可以按照Spring for Apache Pulsar 参考文档中的[这些步骤](https://docs.spring.io/spring-pulsar/docs/1.0.0/reference/reference/pulsar.html#tls-encryption)来启用 TLS 加密。

有关客户端和身份验证的完整详细信息，请参阅 Spring for Apache Pulsar[参考文档](https://docs.spring.io/spring-pulsar/docs/1.0.0/reference/reference/pulsar.html#pulsar-client)。

**10.4.2. 反应式连接到 Pulsar**

当Reactive自动配置被激活时，Spring Boot将自动配置并注册一个`ReactivePulsarClient`bean。

`ReactivePulsarClient`采用了前面描述的`PulsarClient`实例。因此，请按照上一节`PulsarClient`配置`ReactivePulsarClient`.

**10.4.3. 连接到 Pulsar 管理**

Spring for Apache Pulsar 的`PulsarAdministration`客户端也是自动配置的。

默认情况下，应用程序尝试连接到位于`http://localhost:8080` 的本地 Pulsar 实例。这可以通过在表单中将`spring.pulsar.admin.service-url`属性设置为`(http|https)://<host>:<port>`不同的值来调整。

如果您需要对配置进行更多控制，请考虑注册一个或多个`PulsarAdminBuilderCustomizer`bean。

**验证**

当访问需要身份验证的 Pulsar 集群时，管理客户端需要与常规 Pulsar 客户端相同的安全配置。您可以通过`spring.pulsar.admin.authentication`替换为 `spring.pulsar.client.authentication`来使用上述[身份验证配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#messaging.pulsar.connecting.auth)。

> 要在启动时创建主题，请添加类型为`PulsarTopic` 的 bean 。如果主题已经存在，则忽略该 bean。

**10.4.4。发送消息**

Spring`PulsarTemplate`是自动配置的，您可以使用它来发送消息，如以下示例所示：

```
@Component
public class MyBean {
​
    private final PulsarTemplate<String> pulsarTemplate;
​
    public MyBean(PulsarTemplate<String> pulsarTemplate) {
        this.pulsarTemplate = pulsarTemplate;
    }
​
    public void someMethod() throws PulsarClientException {
        this.pulsarTemplate.send("someTopic", "Hello");
    }
​
}
```

`PulsarTemplate`依赖于`PulsarProducerFactory`创建底层的 Pulsar 生产者。Spring Boot 自动配置还提供了这个生产者工厂，默认情况下，它会缓存它创建的生产者。您可以通过指定任何`spring.pulsar.producer.*`和`spring.pulsar.producer.cache.*`前缀的应用程序属性来配置生产者工厂和缓存设置。

如果您需要对生产者工厂配置进行更多控制，请考虑注册一个或多个`ProducerBuilderCustomizer`bean。这些定制器适用于所有创建的生产者。也可以在发送消息时传入a`ProducerBuilderCustomizer`，只影响当前的生产者。

如果您需要对发送的消息进行更多控制，可以在发送消息时传入`TypedMessageBuilderCustomizer` 。

**10.4.5. 反应式发送消息**

当Reactive自动配置被激活时，Spring`ReactivePulsarTemplate`会自动配置，你可以使用它来发送消息，如下例所示：

```
@Component
public class MyBean {
​
    private final ReactivePulsarTemplate<String> pulsarTemplate;
​
    public MyBean(ReactivePulsarTemplate<String> pulsarTemplate) {
        this.pulsarTemplate = pulsarTemplate;
    }
​
    public void someMethod() {
        this.pulsarTemplate.send("someTopic", "Hello").subscribe();
    }
​
}
```

`ReactivePulsarTemplate`依赖于`ReactivePulsarSenderFactory` 来实际创建底层发送者。Spring Boot 自动配置还提供了这个发送者工厂，默认情况下，它会缓存它创建的生产者。您可以通过指定任何`spring.pulsar.producer.*`和`spring.pulsar.producer.cache.*`前缀应用程序属性来配置发件人工厂和缓存设置。

如果您需要对发送者工厂配置进行更多控制，请考虑注册一个或多个`ReactiveMessageSenderBuilderCustomizer`bean。这些定制器适用于所有创建的发件人。也可以在发送消息时传入 `ReactiveMessageSenderBuilderCustomizer` ，只影响当前发送者。

如果您需要对发送的消息进行更多控制，可以在发送消息时传入 `MessageSpecBuilderCustomizer` 。

**10.4.6. 接收消息**

当 Apache Pulsar 基础设施存在时，可以对任何 bean 进行注释`@PulsarListener`以创建侦听器端点。以下组件在`someTopic`主题上创建侦听器端点：

```
@Component
public class MyBean {
​
    @PulsarListener(topics = "someTopic")
    public void processMessage(String content) {
        // ...
    }
​
}
```

Spring Boot 自动配置提供了`PulsarListener` 所需的所有组件，例如`PulsarListenerContainerFactory`用来构造底层 Pulsar 消费者的消费者工厂。您可以通过指定任何`spring.pulsar.listener.*`和`spring.pulsar.consumer.*`前缀的应用程序属性来配置这些组件。

如果您需要对消费者工厂配置进行更多控制，请考虑注册一个或多个`ConsumerBuilderCustomizer`bean。这些定制器适用于工厂创建的所有使用者，因此适用于所有`@PulsarListener`实例。您还可以通过设置`@PulsarListener`注解的`consumerCustomizer`属性来自定义单个监听器。

**10.4.7. 反应性地接收消息**

当 Apache Pulsar 基础设施存在并且响应式自动配置被激活时，可以对任何 bean 进行注释以`@ReactivePulsarListener`创建响应式侦听器端点。以下组件在该`someTopic`主题上创建一个反应式侦听器端点：

```
@Component
public class MyBean {
​
    @ReactivePulsarListener(topics = "someTopic")
    public Mono<Void> processMessage(String content) {
        // ...
        return Mono.empty();
    }
​
}
```

Spring Boot 自动配置提供了 所需的所有组件`ReactivePulsarListener`，例如`ReactivePulsarListenerContainerFactory`它用来构造底层反应式 Pulsar 消费者的消费者工厂。您可以通过指定任&#x4F55;**`spring.pulsar.listener.`**&#x548C;**`spring.pulsar.consumer.`**&#x524D;缀的应用程序属性来配置这些组件。

如果您需要对消费者工厂配置进行更多控制，请考虑注册一个或多个`ReactiveMessageConsumerBuilderCustomizer`bean。这些定制器适用于工厂创建的所有使用者，因此适用于所有`@ReactivePulsarListener`实例。您还可以通过设置`@ReactivePulsarListener`注解的`consumerCustomizer`属性来自定义单个监听器。

**10.4.8. 阅读消息**

Pulsar 阅读器界面使应用程序能够手动管理光标。当您使用阅读器连接到主题时，您需要指定阅读器在连接到主题时从哪条消息开始阅读。

当 Apache Pulsar 基础设施存在时，任何 Bean 都可以通过阅读器进行`@PulsarReader`注释以使用消息。以下组件创建一个读取器端点，该端点从`someTopic`主题的开头开始读取消息：

```
@Component
public class MyBean {
​
    @PulsarReader(topics = "someTopic", startMessageId = "earliest")
    public void processMessage(String content) {
        // ...
    }
​
}
```

`@PulsarReader`依赖于`PulsarReaderFactory`创建底层 Pulsar 阅读器。Spring Boot 自动配置提供了这个读取器工厂，可以通过设置任何带`spring.pulsar.reader.*`前缀的应用程序属性来自定义它。

如果您需要对读取器工厂配置进行更多控制，请考虑注册一个或多个`ReaderBuilderCustomizer`bean。这些定制器适用于工厂创建的所有读取器，因此适用于所有`@PulsarReader`实例。您还可以通过设置注解`@PulsarReader`的`readerCustomizer`属性来自定义单个监听器。

**10.4.9. 反应式地阅读消息**

当 Apache Pulsar 基础设施存在并且响应式自动配置被激活时，将提供 Spring `ReactivePulsarReaderFactory`，您可以使用它创建一个阅读器，以便以响应式方式读取消息。以下组件使用提供的工厂创建一个阅读器，并从`someTopic`主题读取 5 分钟前的一条消息：

```
@Component
public class MyBean {
​
    private final ReactivePulsarReaderFactory<String> pulsarReaderFactory;
​
    public MyBean(ReactivePulsarReaderFactory<String> pulsarReaderFactory) {
        this.pulsarReaderFactory = pulsarReaderFactory;
    }
​
    public void someMethod() {
        ReactiveMessageReaderBuilderCustomizer<String> readerBuilderCustomizer = (readerBuilder) -> readerBuilder
            .topic("someTopic")
            .startAtSpec(StartAtSpec.ofInstant(Instant.now().minusSeconds(5)));
        Mono<Message<String>> message = this.pulsarReaderFactory
            .createReader(Schema.STRING, List.of(readerBuilderCustomizer))
            .readOne();
        // ...
    }
​
}
```

Spring Boot 自动配置提供了这个读取器工厂，可以通过设置任何带`spring.pulsar.reader.*`前缀的应用程序属性来自定义它。

如果您需要对读取器工厂配置进行更多控制，请考虑在使用工厂创建读取器时传递一个或多个`ReactiveMessageReaderBuilderCustomizer`实例。

如果您需要对读取器工厂配置进行更多控制，请考虑注册一个或多个`ReactiveMessageReaderBuilderCustomizer`bean。这些定制器适用于所有创建的阅读器。您还可以在创建阅读器时传递一个或多个`ReactiveMessageReaderBuilderCustomizer`，以仅将自定义应用到创建的阅读器。

> 有关上述任何组件的更多详细信息以及发现其他可用功能，请参阅 Spring for Apache Pulsar[参考文档](https://docs.spring.io/spring-pulsar/docs/1.0.0/reference/)。

**10.4.10. 额外的脉冲星特性**

自动配置支持的属性显示在附录的[“集成属性”部分中。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.application-properties.integration)请注意，在大多数情况下，这些属性（连字符或驼峰命名法）直接映射到 Apache Pulsar 配置属性。有关详细信息，请参阅 Apache Pulsar 文档。

只有 Pulsar 支持的属性的子集可以直接通过`PulsarProperties`类获得。如果您希望使用不直接支持的其他属性来调整自动配置的组件，则可以使用上述每个组件支持的定制器。


# 10.5. RSocket

[RSocket](https://rsocket.io/)是一种用于字节流传输的二进制协议。它通过在单个连接上传递异步消息来实现对称交互模型。

Spring框架的模块`spring-messaging`为客户端和服务器端的RSocket请求者和响应者提供支持。有关更多详细信息，请参阅Spring 框架参考的[RSocket 部分](https://docs.spring.io/spring-framework/reference/6.1/rsocket.html#rsocket-spring)，包括 RSocket 协议的概述。

**10.5.1. RSocket 策略自动配置**

Spring Boot 自动配置一个`RSocketStrategies`bean，提供编码和解码 RSocket 有效负载所需的所有基础设施。默认情况下，自动配置将尝试配置以下内容（按顺序）：

1. Jackson 的[CBOR编解码器](https://cbor.io/)
2. Jackson 的 JSON 编解码器

`spring-boot-starter-rsocket`启动器提供了这两个依赖项。请参阅[Jackson 支持部分](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.json.jackson)以了解有关自定义可能性的更多信息。

开发人员可以通过创建`RSocketStrategies`实现`RSocketStrategiesCustomizer`接口的 bean 来自定义组件。请注意，它们的`@Order`很重要，因为它决定了编解码器的顺序。

**10.5.2. RSocket服务器自动配置**

Spring Boot 提供 RSocket 服务器自动配置。所需的依赖项由`spring-boot-starter-rsocket`提供.

Spring Boot 允许从 WebFlux 服务器通过 WebSocket 公开 RSocket，或者建立独立的 RSocket 服务器。这取决于应用程序的类型及其配置。

对于 WebFlux 应用程序（类型为`WebApplicationType.REACTIVE`），仅当以下属性匹配时，RSocket 服务器才会插入 Web 服务器：

```
spring.rsocket.server.mapping-path=/rsocket
spring.rsocket.server.transport=websocket
```

> 仅 Reactor Netty 支持将 RSocket 插入 Web 服务器，因为 RSocket 本身就是使用该库构建的。

或者，RSocket TCP 或 websocket 服务器作为独立的嵌入式服务器启动。除了依赖性要求之外，唯一需要的配置是为该服务器定义一个端口：

```
spring.rsocket.server.port=9898
```

**10.5.3. Spring Messaging RSocket 支持**

Spring Boot 将为 RSocket 自动配置 Spring Messaging 基础设施。

这意味着 Spring Boot 将创建一个`RSocketMessageHandler`bean 来处理对应用程序的 RSocket 请求。

**10.5.4. 使用 RSocketRequester 调用 RSocket 服务**

一旦服务器和客户端之间建立了`RSocket`通道，任何一方都可以向另一方发送或接收请求。

作为服务器，您可以在 RSocket `@Controller`的任何处理程序方法上注入`RSocketRequester`实例。作为客户端，您需要首先配置并建立RSocket连接。Spring Boot 会使用预期的编解码器自动配置`RSocketRequester.Builder`此类情况并应用任何`RSocketConnectorConfigurer`bean。

`RSocketRequester.Builder`实例是一个原型bean，这意味着每个注入点都会为您提供一个新实例。这是有意完成的，因为此构建器是有状态的，您不应使用同一实例创建具有不同设置的请求者。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final RSocketRequester rsocketRequester;
​
    public MyService(RSocketRequester.Builder rsocketRequesterBuilder) {
        this.rsocketRequester = rsocketRequesterBuilder.tcp("example.org", 9898);
    }
​
    public Mono<User> someRSocketCall(String name) {
        return this.rsocketRequester.route("user").data(name).retrieveMono(User.class);
    }
​
}
```


# 10.6. Spring Integration

Spring Boot 为使用[Spring Integration](https://spring.io/projects/spring-integration)提供了多种便利，包括`spring-boot-starter-integration`“Starter”。Spring Integration 提供了对消息传递以及其他传输（例如 HTTP、TCP 等）的抽象。如果 Spring Integration 在您的类路径上可用，它将通过`@EnableIntegration`注释进行初始化。

Spring Integration 轮询逻辑依赖[于自动配置的`TaskScheduler`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.task-execution-and-scheduling). `PollerMetadata`默认值（每秒轮询无限数量的消息）可以使用`spring.integration.poller.*`配置属性进行自定义。

Spring Boot 还配置了一些由其他 Spring Integration 模块的存在触发的功能。如果`spring-integration-jmx`也在类路径上，则消息处理统计信息将通过 JMX 发布。如果`spring-integration-jdbc`可用，则可以在启动时创建默认数据库架构，如下行所示：

```
spring.integration.jdbc.initialize-schema=always
```

如果`spring-integration-rsocket`可用，开发人员可以使用`"spring.rsocket.server.*"`属性配置 RSocket 服务器，并让它使用`IntegrationRSocketEndpoint`或`RSocketOutboundGateway`组件来处理传入的 RSocket 消息。该基础设施可以处理 Spring Integration RSocket 通道适配器和`@MessageMapping`处理程序（已给定`"spring.integration.rsocket.server.message-mapping-enabled"`配置）。

Spring Boot 还可以使用配置属性自动配置`ClientRSocketConnector`：

```
# Connecting to a RSocket server over TCP
spring.integration.rsocket.client.host=example.org
spring.integration.rsocket.client.port=9898
```

```
# Connecting to a RSocket Server over WebSocket
spring.integration.rsocket.client.uri=ws://example.org
```

有关更多详细信息，请参阅[`IntegrationAutoConfiguration`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationAutoConfiguration.java)和[`IntegrationProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/integration/IntegrationProperties.java)类。


# 10.7. WebSockets

Spring Boot 为嵌入式 Tomcat、Jetty 和 Undertow 提供 WebSockets 自动配置。如果将 war 文件部署到独立容器，Spring Boot 会假定该容器负责配置其 WebSocket 支持。

Spring框架为MVC Web应用程序提供了[丰富的WebSocket支持](https://docs.spring.io/spring-framework/reference/6.1/web/websocket.html)，可以通过`spring-boot-starter-websocket`模块轻松访问。

WebSocket 支持也可用于[反应式 Web 应用程序](https://docs.spring.io/spring-framework/reference/6.1/web/webflux-websocket.html)，并且需要同时包含属于`spring-boot-starter-webflux`的 WebSocket API ：

```
<dependency>
    <groupId>jakarta.websocket</groupId>
    <artifactId>jakarta.websocket-api</artifactId>
</dependency>
```


# 10.8. What to Read Next

下一节将介绍如何在应用程序中启用[IO 功能。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io)您可以在本节中阅读有关[缓存](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching)、[邮件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.email)、[验证](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.validation)、[休息客户端](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.rest-client)等的内容。


# 11. IO

大多数应用程序在某些时候都需要处理输入和输出问题。Spring Boot 提供实用程序并与一系列技术集成，以在您需要 IO 功能时提供帮助。本节涵盖标准 IO 功能（例如缓存和验证）以及更高级的主题（例如调度和分布式事务）。我们还将介绍调用远程 REST 或 SOAP 服务以及发送电子邮件。

####

####

####

####

####

####

####

####

####


# 11.1. 缓存

Spring 框架支持透明地向应用程序添加缓存。其核心是，抽象将缓存应用于方法，从而减少基于缓存中可用信息的执行次数。缓存逻辑是透明应用的，不会对调用者造成任何干扰。只要使用`@EnableCaching`注释启用缓存支持，Spring Boot 就会自动配置缓存基础设施。

> 检查Spring 框架参考的 [相关部分以获取更多详细信息。](https://docs.spring.io/spring-framework/reference/6.1/integration/cache.html)

简而言之，要向服务的操作添加缓存，请向其方法添加相关注释，如以下示例所示：

```
@Component
public class MyMathService {
​
    @Cacheable("piDecimals")
    public int computePiDecimal(int precision) {
        ...
    }
​
}
```

此示例演示了如何在可能成本高昂的操作中使用缓存。在调用之前`computePiDecimal`，抽象会在`piDecimals`缓存中查找与参数`i`匹配的条目。如果找到条目，则立即将缓存中的内容返回给调用者，并且不调用该方法。否则，将调用该方法，并在返回值之前更新缓存。

> 您还可以透明地使用标准 JSR-107 (JCache) 注释（例如`@CacheResult`）。但是，我们强烈建议您不要混合搭配 Spring Cache 和 JCache 注解。

如果您不添加任何特定的缓存库，Spring Boot 会自动配置一个使用内存中并发映射的[简单提供程序。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.simple)当需要缓存时（例如`piDecimals`前面的示例），该提供程序会为您创建它。并不真正建议将简单的提供程序用于生产用途，但它非常适合入门并确保您了解其功能。当您决定要使用的缓存提供程序时，请务必阅读其文档以了解如何配置应用程序使用的缓存。几乎所有提供程序都要求您显式配置应用程序中使用的每个缓存。有些提供了一种自定义属性`spring.cache.cache-names`定义的默认缓存的方法。

> 还可以透明地从缓存中 [更新](https://docs.spring.io/spring-framework/reference/6.1/integration/cache/annotations.html#cache-annotations-put)或[逐出数据。](https://docs.spring.io/spring-framework/reference/6.1/integration/cache/annotations.html#cache-annotations-evict)

**11.1.1. 支持的缓存提供程序**

缓存抽象不提供实际的存储，而是依赖于`org.springframework.cache.Cache`和`org.springframework.cache.CacheManager`接口具体化的抽象。

如果您尚未定义类型`CacheManager`或`CacheResolver`命名的 `cacheResolver` bean（请参阅 参考资料[`CachingConfigurer`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/cache/annotation/CachingConfigurer.html)），Spring Boot 会尝试检测以下提供程序（按指定的顺序）：

1. [Generic](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.generic)
2. [JCache (JSR-107)](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.jcache) (EhCache 3, Hazelcast, Infinispan, and others)
3. [Hazelcast](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.hazelcast)
4. [Infinispan](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.infinispan)
5. [Couchbase](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.couchbase)
6. [Redis](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.redis)
7. [Caffeine](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.caffeine)
8. [Cache2k](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.cache2k)
9. [Simple](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.simple)

此外，[Spring Boot for Apache Geode](https://github.com/spring-projects/spring-boot-data-geode)提供了[使用 Apache Geode 作为缓存提供程序的自动配置](https://docs.spring.io/spring-boot-data-geode-build/2.0.x/reference/html5/#geode-caching-provider)。

> 如果Spring Boot 自动配置`CacheManager`，则可以通过设置`spring.cache.type`属性来*强制使用*特定的缓存提供程序。如果您需要在某些环境（例如测试）中 [使用无操作缓存，请使用此属性。](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.none)
>
> 使用`spring-boot-starter-cache`“Starter”快速添加基本的缓存依赖项。启动器引入了`spring-context-support`。如果手动添加依赖项，则必须添加依赖项`spring-context-support`才能使用 JCache 或 Caffeine 支持。

如果Spring Boot 自动配置`CacheManager`，您可以在完全初始化之前通过公开实现`CacheManagerCustomizer`接口的 bean 来进一步调整其配置。以下示例设置一个标志来表示`null`值不应向下传递到底层映射：

```
@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {
​
    @Bean
    public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
        return (cacheManager) -> cacheManager.setAllowNullValues(false);
    }
​
}
```

> 在前面的示例中，需要 自动配置 `ConcurrentMapCacheManager`。如果情况并非如此（您提供了自己的配置或自动配置了不同的缓存提供程序），则根本不会调用定制器。您可以拥有任意数量的定制器，也可以使用`@Order`或`Ordered`来进行排序。

**通用的**

如果上下文定义了*至少*一个`org.springframework.cache.Cache`bean，则使用通用缓存。`CacheManager`创建一个包装该类型的所有 bean。

**JCache (JSR-107)**

[JCache](https://jcp.org/en/jsr/detail?id=107)通过类路径上存在的 `javax.cache.spi.CachingProvider`进行引导（即，类路径上存在符合 JSR-107 的缓存库），并且由`spring-boot-starter-cache` “Starter”提供`JCacheCacheManager`。提供各种兼容的库，而且Spring Boot 为 Ehcache 3、Hazelcast 和 Infinispan 提供依赖管理。也可以添加任何其他兼容的库。

可能会出现多个提供者，在这种情况下必须显式指定该提供者。即使 JSR-107 标准没有强制采用标准化方法来定义配置文件的位置，Spring Boot 也会尽力通过实现细节来设置缓存，如以下示例所示：

```
# Only necessary if more than one provider is present
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml
```

> 当缓存库同时提供本机实现和 JSR-107 支持时，Spring Boot 更喜欢 JSR-107 支持，以便在切换到不同的 JSR-107 实现时可以使用相同的功能。
>
> Spring Boot[对 Hazelcast 具有普遍支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.hazelcast)。如果单个`HazelcastInstance`可用，则它也会自动重用`CacheManager`，除非指定了`spring.cache.jcache.config`属性。

底层定制`javax.cache.cacheManager`有两种方式：

* 可以通过设置`spring.cache.cache-names`属性在启动时创建缓存。如果`javax.cache.configuration.Configuration`定义了自定义 bean，则它用于自定义它们。
* `org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer`beans 通过`CacheManager` 的引用来调用以实现完全定制。

> 如果定义了 标准`javax.cache.CacheManager`bean，它会自动包装在`org.springframework.cache.CacheManager`抽象期望的实现中。没有对其应用进一步的定制。

**Hazelcast**

Spring Boot[对 Hazelcast 具有普遍支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.hazelcast)。如果 `HazelcastInstance`已自动配置并且`com.hazelcast:hazelcast-spring`位于类路径上，则它会自动包装在`CacheManager`.

> Hazelcast 可以用作 JCache 兼容缓存或 Spring`CacheManager`兼容缓存。当设置`spring.cache.type`为 `hazelcast`时，Spring Boot 将使用基于`CacheManager`的实现。如果您想使用 Hazelcast 作为 JCache 兼容缓存，请设置`spring.cache.type`为`jcache`。如果您有多个符合 JCache 标准的缓存提供程序并希望强制使用 Hazelcast，则必须[显式设置 JCache 提供程序](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.jcache)。

**Infinispan**

[Infinispan](https://infinispan.org/)没有默认的配置文件位置，因此必须显式指定。否则，将使用默认引导程序。

```
spring.cache.infinispan.config=infinispan.xml
```

可以通过设置`spring.cache.cache-names`属性在启动时创建缓存。如果定义了自定义`ConfigurationBuilder` bean，则它用于自定义缓存。

为了与 Spring Boot 的 Jakarta EE 9 基线兼容，必须使用 Infinispan 的`-jakarta`模块。对于每个具有`-jakarta`变体的模块，必须使用该变体来代替标准模块。例如，`infinispan-core-jakarta`和`infinispan-commons-jakarta`必须分别用来代替`infinispan-core`和 `infinispan-commons`。

**Couchbase**

如果 Spring Data Couchbase 可用并且 Couchbase 已[配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.nosql.couchbase)，则 `CouchbaseCacheManager`会自动配置。可以通过设置`spring.cache.cache-names`属性在启动时创建额外的缓存，并且可以使用`spring.cache.couchbase.*`属性配置缓存默认值。例如，以下配置创建`cache1`并`cache2`缓存条目*过期时间*为 10 分钟：

```
spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m
```

如果您需要对配置进行更多控制，请考虑注册`CouchbaseCacheManagerBuilderCustomizer`bean。以下示例显示了为`cache1`和`cache2`配置特定条目到期时间的定制程序：

```
@Configuration(proxyBeanMethods = false)
public class MyCouchbaseCacheManagerConfiguration {
​
    @Bean
    public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache1", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofSeconds(10)))
                .withCacheConfiguration("cache2", CouchbaseCacheConfiguration
                        .defaultCacheConfig().entryExpiry(Duration.ofMinutes(1)));
​
    }
​
}
```

**Redis**

如果[Redis](https://redis.io/)可用并已配置，则会`RedisCacheManager`自动配置。可以通过设置`spring.cache.cache-names`属性在启动时创建额外的缓存，并且可以使用`spring.cache.redis.*`属性配置缓存默认值。例如，以下配置创建`cache1`并`cache2`缓存*生存时间*为 10 分钟的内容：

```
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m
```

> 默认情况下，会添加一个键前缀，这样，如果两个单独的缓存使用相同的键，Redis 不会有重叠的键，也不会返回无效值。如果您创建自己的`RedisCacheManager`.
>
> 您可以通过添加自己的`RedisCacheConfiguration` `@Bean` 配置来完全控制默认配置。如果您需要自定义默认序列化策略，这可能很有用。

如果您需要对配置进行更多控制，请考虑注册`RedisCacheManagerBuilderCustomizer`bean。以下示例显示了配置特定生存时间`cache1`和 `cache2`的定制程序：

```
@Configuration(proxyBeanMethods = false)
public class MyRedisCacheManagerConfiguration {
​
    @Bean
    public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
        return (builder) -> builder
                .withCacheConfiguration("cache1", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofSeconds(10)))
                .withCacheConfiguration("cache2", RedisCacheConfiguration
                        .defaultCacheConfig().entryTtl(Duration.ofMinutes(1)));
​
    }
​
}
```

**Caffeine**

[Caffeine](https://github.com/ben-manes/caffeine)是 Guava 缓存的 Java 8 重写，取代了对 Guava 的支持。如果存在Caffeine，则会自动配置`CaffeineCacheManager`（由“Starter”提供）。`spring-boot-starter-cache`可以通过设置`spring.cache.cache-names`属性在启动时创建缓存，并且可以通过以下选项之一进行自定义（按指定的顺序）：

1. 缓存规范定义为`spring.cache.caffeine.spec`
2. 定义了一个`com.github.benmanes.caffeine.cache.CaffeineSpec`bean
3. 定义了一个`com.github.benmanes.caffeine.cache.Caffeine`bean

例如，以下配置创建`cache1`和`cache2`缓存的最大大小为 500，*生存时间*为 10 分钟

```
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
```

如果定义了一个`com.github.benmanes.caffeine.cache.CacheLoader` bean，它会自动关联到`CaffeineCacheManager`. 由于`CacheLoader`将会与缓存管理器管理的*所有*缓存相关联，因此它必须定义为`CacheLoader<Object, Object>`。自动配置会忽略任何其他通用类型。

**Cache2k**

[Cache2k](https://cache2k.org/)是内存缓存。如果存在 Cache2k spring 集成，则会自动配置 `SpringCache2kCacheManager`。

可以通过设置`spring.cache.cache-names`属性在启动时创建缓存。可以使用`Cache2kBuilderCustomizer`bean 自定义缓存默认值。以下示例显示了一个自定义程序，它将缓存容量配置为 200 个条目，过期时间为 5 分钟：

```
@Configuration(proxyBeanMethods = false)
public class MyCache2kDefaultsConfiguration {
​
    @Bean
    public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() {
        return (builder) -> builder.entryCapacity(200)
                .expireAfterWrite(5, TimeUnit.MINUTES);
    }
​
}
```

**Simple**

如果找不到其他提供程序，则配置使用 `ConcurrentHashMap`作为缓存存储的简单实现。如果您的应用程序中不存在缓存库，则这是默认设置。默认情况下，会根据需要创建缓存，但您可以通过设置属性`cache-names`来限制可用缓存的列表。例如，如果您只需要`cache1`和`cache2`缓存，请`cache-names`按如下方式设置该属性：

```
spring.cache.cache-names=cache1,cache2
```

如果您这样做并且您的应用程序使用未列出的缓存，那么它会在需要缓存时在运行时失败，但在启动时不会失败。如果您使用未声明的缓存，这类似于“真实”缓存提供程序的行为方式。

**None**

当您的配置中存在`@EnableCaching` 时，也需要合适的缓存配置。如果您有自定义`CacheManager`，请考虑将其定义在单独的`@Configuration`类中，以便您可以在必要时覆盖它。None 使用在测试中有用的无操作实现，切片测试默认通过`@AutoConfigureCache`.

如果您需要在特定环境下使用无操作缓存而不是自动配置的缓存管理器，请将缓存类型设置为`none`，如下例所示：

```
spring.cache.type=none
```


# 11.2. Hazelcast

如果 Hazelcast 位于类路径上并且找到合适的配置，Spring Boot 会自动配置一个`HazelcastInstance`，您可以将其注入到应用程序中。

Spring Boot 首先尝试通过检查以下配置选项来创建客户端：

* `com.hazelcast.client.config.ClientConfig`的存在。
* 由`spring.hazelcast.config`属性定义的配置文件。
* `hazelcast.client.config`系统属性的存在。
* 位于工作目录或类路径的根目录中的`hazelcast-client.xml`。
* `hazelcast-client.yaml`（或`hazelcast-client.yml`）位于工作目录或类路径的根目录中。

如果无法创建客户端，Spring Boot 会尝试配置嵌入式服务器。如果您定义了一个`com.hazelcast.config.Config`bean，Spring Boot 就会使用它。如果您的配置定义了实例名称，Spring Boot 会尝试查找现有实例而不是创建新实例。

您还可以通过配置指定要使用的 Hazelcast 配置文件，如以下示例所示：

```
spring.hazelcast.config=classpath:config/my-hazelcast.xml
```

否则，Spring Boot 会尝试从默认位置查找 Hazelcast 配置：`hazelcast.xml`在工作目录中或类路径的根目录中，或者在相同位置中的 YAML 对应项中。我们还检查系统属性`hazelcast.config`是否已设置。有关更多详细信息，请参阅[Hazelcast 文档。](https://docs.hazelcast.org/docs/latest/manual/html-single/)

> 默认情况下，支持`Hazelcast`组件上的 `@SpringAware`。可以通过声明 `@Order` 大于零的`HazelcastConfigCustomizer`bean 来覆盖`ManagementContext`。
>
> Spring Boot 还[对 Hazelcast 提供显式缓存支持](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.caching.provider.hazelcast)。如果启用缓存，则`HazelcastInstance`会自动包装在`CacheManager`实现中。


# 11.3. Quartz 调度程序

Spring Boot 为使用[Quartz 调度程序](https://www.quartz-scheduler.org/)提供了多种便利，包括`spring-boot-starter-quartz`“Starter”。如果 Quartz 可用，则 `Scheduler`会自动配置（通过抽象的`SchedulerFactoryBean`）。

以下类型的 Bean 会自动选取并与`Scheduler` 相关联：

* `JobDetail`：定义特定的作业。可以使用`JobBuilder` API 构建 `JobDetail`实例。
* `Calendar`。
* `Trigger`：定义何时触发特定作业。

默认情况下，使用内存中的`JobStore`。但是，如果应用程序中存在可用的`DataSource` bean 并且相应配置了`spring.quartz.job-store-type`属性，则可以配置基于 JDBC 的存储，如以下示例所示：

```
spring.quartz.job-store-type=jdbc
```

当使用 JDBC 存储时，可以在启动时初始化架构，如以下示例所示：

```
spring.quartz.jdbc.initialize-schema=always
```

> 默认情况下，使用 Quartz 库提供的标准脚本检测并初始化数据库。这些脚本会删除现有表，并在每次重新启动时删除所有触发器。还可以通过设置`spring.quartz.jdbc.schema`属性来提供自定义脚本。

要让 Quartz 使用应用程序主数据源以外的数据源，请声明一个数据源 bean，并使用 `@QuartzDataSource`注释其`@Bean`方法。这样做可以确保 `SchedulerFactoryBean` 和模式初始化都使用 Quartz 特定的数据源。类似地，要让 Quartz 使用除应用程序的主 `TransactionManager` 之外的`TransactionManager`，请声明一个 `TransactionManager`bean，并使用 `@QuartzTransactionManager` 注释其`@Bean` 方法。

默认情况下，配置创建的作业不会覆盖从持久作业存储中读取的已注册作业。要启用覆盖现有作业定义，请设置`spring.quartz.overwrite-existing-jobs`属性。

Quartz Scheduler 配置可以使用`spring.quartz`属性和`SchedulerFactoryBeanCustomizer` bean进行定制，从而允许编程定制`SchedulerFactoryBean`。可以使用`spring.quartz.properties.*` 来定制高级 Quartz 配置属性。

> 特别是，`Executor`bean 不与调度程序关联，因为 Quartz 提供了一种通过`spring.quartz.properties`配置调度程序的方法。如果您需要自定义任务执行器，请考虑实现`SchedulerFactoryBeanCustomizer`.

作业可以定义设置器来注入数据映射属性。普通bean也可以通过类似的方式注入，如下例所示：

```
public class MySampleJob extends QuartzJobBean {
​
    // fields ...
​
    // Inject "MyService" bean
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
​
    // Inject the "name" job data property
    public void setName(String name) {
        this.name = name;
    }
​
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        this.myService.someMethod(context.getFireTime(), this.name);
    }
​
}
```


# 11.4. 发送电子邮件

Spring 框架提供了使用`JavaMailSender`接口发送电子邮件的抽象，Spring Boot 为其提供了自动配置以及启动模块。

> 有关如何使用 的详细说明， 请参阅[`JavaMailSender`参考文档](https://docs.spring.io/spring-framework/reference/6.1/integration/email.html)。

如果`spring.mail.host`和相关库（由`spring-boot-starter-mail` 定义）可用，则创建默认`JavaMailSender`（如果不存在）。发送者可以通过`spring.mail`命名空间中的配置项进一步定制。请参阅[`MailProperties`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java)了解更多详情。

特别是，某些默认超时值是无限的，您可能需要更改该值以避免线程被无响应的邮件服务器阻塞，如以下示例所示：

```
spring.mail.properties[mail.smtp.connectiontimeout]=5000
spring.mail.properties[mail.smtp.timeout]=3000
spring.mail.properties[mail.smtp.writetimeout]=5000
```

还可以使用 JNDI 中的现有`Session`配置进行`JavaMailSender`配置：

```
spring.mail.jndi-name=mail/Session
```

设置`jndi-name` 后，它优先于所有其他与会话相关的设置。


# 11.5. 验证

只要 JSR-303 实现（例如 Hibernate 验证器）位于类路径上，Bean Validation 1.1 支持的方法验证功能就会自动启用。这使得 bean 方法可以通过`jakarta.validation`对其参数和/或返回值的约束进行注释。具有`@Validated`类注释方法的目标类需要在类型级别使用注释进行注释，以便在其方法中搜索内联约束注释。

例如，以下服务触发第一个参数的验证，确保其大小在 8 到 10 之间：

```
@Service
@Validated
public class MyBean {
​
    public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code, Author author) {
        return ...
    }
​
}
```

在解析约束消息`{parameters}`时使用应用程序的`MessageSource`消息。这允许您将[应用程序的`messages.properties`文件](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.internationalization)用于 Bean 验证消息。一旦参数被解析，消息插值就使用 Bean Validation 的默认插值器完成。

要自定义`Configuration`用于构建`ValidatorFactory`，请定义一个`ValidationConfigurationCustomizer`bean。当定义多个定制器 bean 时，将根据它们的`@Order`注释或实现`Ordered`按顺序调用它们。


# 11.6. 调用 REST 服务

Spring Boot 提供了各种便捷的方式来调用远程 REST 服务。如果您正在开发非阻塞反应式应用程序并且正在使用 Spring WebFlux，那么您可以使用`WebClient`. 如果您更喜欢阻止 API，那么您可以使用`RestClient`或`RestTemplate`。

**11.6.1. WebClient**

如果您的类路径上有 Spring WebFlux，我们建议您使用`WebClient`来调用远程 REST 服务。`WebClient`界面提供了函数式 API，并且是完全响应式的。[您可以在 Spring 框架文档的](https://docs.spring.io/spring-framework/reference/6.1/web/webflux-webclient.html)`WebClient`专门部分了解更多相关信息。

> 如果您不编写响应式 Spring WebFlux 应用程序，则可以使用[`RestClient`](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#io.rest-client.restclient)替代`WebClient`. 这提供了类似的功能 API，但是阻塞式的而不是响应式的。

Spring Boot为您创建并预配置原型`WebClient.Builder`bean。强烈建议将其注入您的组件中并使用它来创建`WebClient`实例。Spring Boot 正在配置该构建器以共享 HTTP 资源并以与服务器相同的方式反映编解码器设置（请参阅[WebFlux HTTP 编解码器自动配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.reactive.webflux.httpcodecs)）等等。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final WebClient webClient;
​
    public MyService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").build();
    }
​
    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
    }
​
}
```

**WebClient运行时**

Spring Boot 将根据应用程序类路径上可用的库自动检测使用哪个 `ClientHttpConnector` 来驱动`WebClient`。按照优先顺序，支持以下客户端：

1. Reactor Netty
2. Jetty RS 客户端
3. Apache HttpClient
4. JDK HttpClient

如果类路径上有多个客户端可用，则将使用最首选的客户端。

默认情况下，启动器`spring-boot-starter-webflux`依赖于`io.projectreactor.netty:reactor-netty`服务器和客户端实现。如果您选择使用 Jetty 作为反应式服务器，则应添加对 Jetty Reactive HTTP 客户端库 的依赖项`org.eclipse.jetty:jetty-reactive-httpclient`。服务器和客户端使用相同的技术有其优点，因为它将自动在客户端和服务器之间共享 HTTP 资源。

开发人员可以通过提供自定义`ReactorResourceFactory`或`JettyResourceFactory`bean来覆盖 Jetty 和 Reactor Netty 的资源配置- 这将应用于客户端和服务器。

如果您希望覆盖客户端的选择，您可以定义自己的`ClientHttpConnector`bean 并完全控制客户端配置。

[您可以在 Spring 框架`WebClient`参考文档中](https://docs.spring.io/spring-framework/reference/6.1/web/webflux-webclient/client-builder.html)了解有关配置选项的更多信息。

**WebClient定制**

自定义有三种主要方法`WebClient`，具体取决于您希望自定义应用的范围。

为了使任何自定义的范围尽可能缩小，请注入自动配置的`WebClient.Builder`内容，然后根据需要调用其方法。 `WebClient.Builder`实例是有状态的：构建器上的任何更改都会反映在随后使用它创建的所有客户端中。如果您想使用同一个构建器创建多个客户端，您还可以考虑使用`WebClient.Builder other = builder.clone();`.

要对所有`WebClient.Builder`实例进行应用程序范围的附加自定义，您可以声明`WebClientCustomizer`bean 并在注入点位置更改`WebClient.Builder`。

最后，您可以回退到原始 API 并使用`WebClient.create()`. 在这种情况下，不会应用任何`WebClientCustomizer`的自动配置。

**WebClient SSL 支持**

如果您需要在`ClientHttpConnector`所使用的`WebClient`上进行自定义 SSL 配置，您可以注入一个`WebClientSsl`可与构建器`apply`方法一起使用的实例。

该界面提供对您在`application.properties`或`application.yaml`文件中定义的任何[SSL 捆绑包的](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl.bundles)`WebClientSsl`访问。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final WebClient webClient;
​
    public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
    }
​
    public Mono<Details> someRestCall(String name) {
        return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
    }
​
}
```

**11.6.2. Rest 客户端**

如果您在应用程序中没有使用 Spring WebFlux 或 Project Reactor，我们建议您使用`RestClient`调用远程 REST 服务。

`RestClient`接口提供了函数式阻塞API。

Spring Boot为您创建并预配置原型`RestClient.Builder`bean。强烈建议将其注入您的组件中并使用它来创建`RestClient`实例。Spring Boot 正在使用`HttpMessageConverters` 和适当的 `ClientHttpRequestFactory` 配置该构建器。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final RestClient restClient;
​
    public MyService(RestClient.Builder restClientBuilder) {
        this.restClient = restClientBuilder.baseUrl("https://example.org").build();
    }
​
    public Details someRestCall(String name) {
        return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
    }
​
}
```

**RestClient定制**

自定义有三种主要方法`RestClient`，具体取决于您希望自定义应用的范围。

为了使任何自定义的范围尽可能缩小，请注入自动配置的`RestClient.Builder`内容，然后根据需要调用其方法。

`RestClient.Builder`实例是有状态的：构建器上的任何更改都会反映在随后使用它创建的所有客户端中。如果您想使用同一个构建器创建多个客户端，您还可以考虑使用`RestClient.Builder other = builder.clone();`.

要对所有`RestClient.Builder`实例进行应用程序范围的附加自定义，您可以声明`RestClientCustomizer`bean 并在注入点位置更改`RestClient.Builder`。

最后，您可以回退到原始 API 并使用`RestClient.create()`. 在这种情况下，不会应用任何`RestClientCustomizer`的自动配置。

**RestClient SSL 支持**

如果您需要在`ClientHttpRequestFactory`所使用的`RestClient`上进行自定义 SSL 配置，您可以注入一个`RestClientSsl`可与构建器`apply`方法一起使用的实例。

该界面提供对您在`application.properties`或`application.yaml`文件中定义的任何[SSL 捆绑包的](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl.bundles)`RestClientSsl`访问。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final RestClient restClient;
​
    public MyService(RestClient.Builder restClientBuilder, RestClientSsl ssl) {
        this.restClient = restClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
    }
​
    public Details someRestCall(String name) {
        return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
    }
​
}
```

如果除了 SSL 捆绑包之外您还需要应用其他自定义，则可以将`ClientHttpRequestFactorySettings`类与`ClientHttpRequestFactories`一起使用：

```
@Service
public class MyService {
​
    private final RestClient restClient;
​
    public MyService(RestClient.Builder restClientBuilder, SslBundles sslBundles) {
        ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.DEFAULTS
            .withReadTimeout(Duration.ofMinutes(2))
            .withSslBundle(sslBundles.getBundle("mybundle"));
        ClientHttpRequestFactory requestFactory = ClientHttpRequestFactories.get(settings);
        this.restClient = restClientBuilder.baseUrl("https://example.org").requestFactory(requestFactory).build();
    }
​
    public Details someRestCall(String name) {
        return this.restClient.get().uri("/{name}/details", name).retrieve().body(Details.class);
    }
​
}
```

**11.6.3. RestTemplate**

Spring Framework 的[`RestTemplate`](https://docs.spring.io/spring-framework/docs/6.1.1/javadoc-api/org/springframework/web/client/RestTemplate.html)类早于`RestClient`并且是许多应用程序用来调用远程 REST 服务的经典方式。`RestTemplate`当您不想迁移到现有代码`RestClient`，或者因为您已经熟悉该API 时，您可能会选择使用`RestTemplate`。

由于`RestTemplate`实例在使用之前通常需要进行自定义，因此 Spring Boot 不提供任何单个自动配置的`RestTemplate`bean。但是，它会自动配置 `RestTemplateBuilder`，可用于在需要时创建`RestTemplate`实例。自动配置`RestTemplateBuilder`可确保将合理`HttpMessageConverters`和适当的`ClientHttpRequestFactory`应用到`RestTemplate`实例。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final RestTemplate restTemplate;
​
    public MyService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }
​
    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    }
​
}
```

`RestTemplateBuilder`包括许多有用的方法，可用于快速配置`RestTemplate`. 例如，要添加 BASIC 身份验证支持，您可以使用`builder.basicAuthentication("user", "password").build()`.

**RestTemplate定制**

自定义有三种主要方法`RestTemplate`，具体取决于您希望自定义应用的范围。

为了使任何自定义的范围尽可能缩小，请注入自动配置的内容`RestTemplateBuilder`，然后根据需要调用其方法。每个方法调用都会返回一个新`RestTemplateBuilder`实例，因此自定义仅影响构建器的这种使用。

要进行应用程序范围的附加定制，请使用`RestTemplateCustomizer`bean。所有此类 bean 都会自动注册到自动配置`RestTemplateBuilder`，并应用于使用它构建的任何模板。

以下示例显示了一个定制程序，该定制程序为除以下主机`192.168.0.5`之外的所有主机配置代理的使用：

```
public class MyRestTemplateCustomizer implements RestTemplateCustomizer {
​
    @Override
    public void customize(RestTemplate restTemplate) {
        HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));
        HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
    }
​
    static class CustomRoutePlanner extends DefaultProxyRoutePlanner {
​
        CustomRoutePlanner(HttpHost proxy) {
            super(proxy);
        }
​
        @Override
        protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {
            if (target.getHostName().equals("192.168.0.5")) {
                return null;
            }
            return super.determineProxy(target, context);
        }
​
    }
​
}
```

最后，您可以定义自己的`RestTemplateBuilder`bean。这样做将替换自动配置的构建器。如果您希望将任何`RestTemplateCustomizer`bean 应用于您的自定义构建器（就像自动配置一样），请使用`RestTemplateBuilderConfigurer`. 以下示例公开了与 Spring Boot 的自动配置将执行的操作相匹配的`RestTemplateBuilder` ，除此之外还指定了自定义连接和读取超时：

```
@Configuration(proxyBeanMethods = false)
public class MyRestTemplateBuilderConfiguration {
​
    @Bean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
        return configurer.configure(new RestTemplateBuilder())
            .setConnectTimeout(Duration.ofSeconds(5))
            .setReadTimeout(Duration.ofSeconds(2));
    }
​
}
```

最极端（且很少使用）的选项是在不使用配置器的情况下创建您自己的`RestTemplateBuilder`bean。除了替换自动配置的构建器之外，这还可以防止使用任何`RestTemplateCustomizer` bean。

**RestTemplate SSL 支持**

如果您需要在 上进行自定义 SSL 配置`RestTemplate`，您可以将[SSL 捆绑包](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.ssl.bundles)应用于 `RestTemplateBuilder`，如本示例所示：

```
@Service
public class MyService {
​
    private final RestTemplate restTemplate;
​
    public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
        this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build();
    }
​
    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    }
​
}
```

**11.6.4. RestClient 和 RestTemplate 的 HTTP 客户端检测**

Spring Boot 将根据应用程序类路径上可用的库自动检测与 `RestClient` 和`RestTemplate` 一起使用的 HTTP 客户端。按照优先顺序，支持以下客户端：

1. Apache HttpClient
2. Jetty HttpClient
3. OkHttp（已弃用）
4. 简单的 JDK 客户端 ( `HttpURLConnection`)

如果类路径上有多个客户端可用，则将使用最首选的客户端。


# 11.7. web services

Spring Boot 提供 Web 服务自动配置，因此您所要做的就是定义您的`Endpoints`.

使用`spring-boot-starter-webservices`模块可以轻松访问[Spring Web Services](https://docs.spring.io/spring-ws/docs/4.0.8/reference/html/)功能。

`SimpleWsdl11Definition`和`SimpleXsdSchema`beans 可以分别为您的 WSDL 和 XSD 机型自动创建。为此，请配置它们的位置，如以下示例所示：

```
spring.webservices.wsdl-locations=classpath:/wsdl
```

**11.7.1. 使用 WebServiceTemplate 调用 Web 服务**

如果您需要从应用程序调用远程 Web 服务，则可以使用该类[`WebServiceTemplate`](https://docs.spring.io/spring-ws/docs/4.0.8/reference/html/#client-web-service-template)。由于`WebServiceTemplate`实例在使用之前通常需要进行自定义，因此 Spring Boot 不提供任何单个自动配置的`WebServiceTemplate`bean。但是，它会自动配置 `WebServiceTemplateBuilder`，可用于在需要时创建`WebServiceTemplate`实例。

下面的代码展示了一个典型的例子：

```
@Service
public class MyService {
​
    private final WebServiceTemplate webServiceTemplate;
​
    public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {
        this.webServiceTemplate = webServiceTemplateBuilder.build();
    }
​
    public SomeResponse someWsCall(SomeRequest detailsReq) {
        return (SomeResponse) this.webServiceTemplate.marshalSendAndReceive(detailsReq,
                new SoapActionCallback("https://ws.example.com/action"));
    }
​
}
```

默认情况下，`WebServiceTemplateBuilder` 使用类路径上的可用 HTTP 客户端库检测合适的基于 HTTP 的 `WebServiceMessageSender`。您还可以自定义读取和连接超时，如下所示：

```
@Configuration(proxyBeanMethods = false)
public class MyWebServiceTemplateConfiguration {
​
    @Bean
    public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {
        WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder()
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(2))
                .build();
        return builder.messageSenders(sender).build();
    }
​
}
```


# 11.8. 使用 JTA 进行分布式事务

Spring Boot 通过使用从 JNDI 检索的事务管理器支持跨多个 XA 资源的分布式 JTA 事务。

当检测到 JTA 环境时，Spring`JtaTransactionManager`将用于管理事务。自动配置的 JMS、DataSource 和 JPA bean 已升级以支持 XA 事务。您可以使用标准 Spring 习惯用法（例如 `@Transactional`）来参与分布式事务。如果您在 JTA 环境中并且仍想使用本地事务，则可以将`spring.jta.enabled`属性设置为`false`来禁用 JTA 自动配置。

**11.8.1. 使用 Jakarta EE 托管事务管理器**

如果将 Spring Boot 应用程序打包为`war`或`ear`文件并将其部署到 Jakarta EE 应用程序服务器，则可以使用应用程序服务器的内置事务管理器。Spring Boot 尝试通过查看常见的 JNDI 位置（`java:comp/UserTransaction` 、 `java:comp/TransactionManager`等）来自动配置事务管理器。当使用应用程序服务器提供的事务服务时，您通常还希望确保所有资源都由服务器管理并通过 JNDI 公开。`ConnectionFactory`Spring Boot 尝试通过在 JNDI 路径（`java:/JmsXA`或`java:/XAConnectionFactory`）中查找来自动配置 JMS ，并且您可以使用[`spring.datasource.jndi-name`属性](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#data.sql.datasource.jndi)来配置您的`DataSource`.

**11.8.2. 混合 XA 和非 XA JMS 连接**

使用 JTA 时，主 JMS `ConnectionFactory`bean 是 XA 感知的并参与分布式事务。您可以注入到您的 bean 中，无需使用任何`@Qualifier`：

```
public MyBean(ConnectionFactory connectionFactory) {
    // ...
}
```

在某些情况下，您可能希望使用非 XA `ConnectionFactory`来处理某些 JMS 消息。例如，您的 JMS 处理逻辑可能需要比 XA 超时更长的时间。

如果你想使用非 XA `ConnectionFactory`，你可以使用`nonXaJmsConnectionFactory`bean：

```
public MyBean(@Qualifier("nonXaJmsConnectionFactory") ConnectionFactory connectionFactory) {
    // ...
}
```

为了保持一致性，`jmsConnectionFactory`还使用 bean 别`xaJmsConnectionFactory`名来提供 bean ：

```
public MyBean(@Qualifier("xaJmsConnectionFactory") ConnectionFactory connectionFactory) {
    // ...
}
```

**11.8.3. 支持嵌入式事务管理器**

[`XAConnectionFactoryWrapper`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jms/XAConnectionFactoryWrapper.java)和[`XADataSourceWrapper`](https://github.com/spring-projects/spring-boot/tree/v3.2.0/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/XADataSourceWrapper.java)接口可用于支持嵌入式事务管理器。这些接口负责包装`XAConnectionFactory`和`XADataSource`Bean并将它们公开为常规`ConnectionFactory` 和`DataSource`Bean ，从而透明地注册到分布式事务中。DataSource 和 JMS 自动配置使用 JTA 变体，前提是您在 `ApplicationContext`中注册了`JtaTransactionManager` bean 和适当的 XA 包装器 bean。


# 11.9. 接下来读什么

您现在应该很好地了解了 Spring Boot 的[核心功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features)以及 Spring Boot 通过自动配置提供支持的各种技术。

接下来的几节将详细介绍如何将应用程序部署到云平台。您可以在下一节中阅读有关[构建容器映像的信息，或跳至](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images)[生产就绪功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator)部分。


# 12. 容器镜像

Spring Boot 应用程序可以[使用 Dockerfile](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images.dockerfiles)进行容器化，或者[使用 Cloud Native Buildpack 来创建可在任何地方运行的优化的 docker 兼容容器映像](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images.buildpacks)，例如Kubernetes。

#### 12.1. 高效的容器镜像

可以轻松地将 Spring Boot uber jar 打包为 docker 镜像。然而，像在 docker 镜像中那样复制和运行 uber jar 存在各种缺点。在不解压的情况下运行 uber jar 总是会产生一定量的开销，并且在容器化环境中这可能会很明显。另一个问题是，将应用程序的代码及其所有依赖项放在 Docker 映像的一层中并不是最佳选择。由于您重新编译代码的频率可能比升级所使用的 Spring Boot 版本的频率高，因此最好将各个部分分开一些。如果将 jar 文件放在应用程序类之前的层中，Docker 通常只需要更改最底层，就可以从其缓存中选取其他层。

**12.1.1. 对 Docker 镜像进行分层**

为了更轻松地创建优化的 Docker 镜像，Spring Boot 支持向 jar 添加层索引文件。它提供了层列表以及应包含在其中的 jar 部分。索引中的层列表根据层应添加到 Docker/OCI 映像的顺序进行排序。开箱即用，支持以下层：

* `dependencies`（对于定期发布的依赖项）
* `spring-boot-loader`（对于以下所有内容`org/springframework/boot/loader`）
* `snapshot-dependencies`（对于快照依赖项）
* `application`（对于应用程序类和资源）

下面显示了一个`layers.idx`文件的示例：

```
- "dependencies":
  - BOOT-INF/lib/library1.jar
  - BOOT-INF/lib/library2.jar
- "spring-boot-loader":
  - org/springframework/boot/loader/launch/JarLauncher.class
  - ... <other classes>
- "snapshot-dependencies":
  - BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
  - META-INF/MANIFEST.MF
  - BOOT-INF/classes/a/b/C.class
```

这种分层旨在根据应用程序构建之间更改的可能性来分离代码。库代码在构建之间不太可能发生更改，因此将其放置在自己的层中，以允许工具重用缓存中的层。应用程序代码更有可能在构建之间发生更改，因此它被隔离在单独的层中。

Spring Boot 还借助`layers.idx`.

对于 Maven，请参阅[打包分层 jar 或 war 部分，](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/htmlsingle/#repackage-layers)了解有关向存档添加层索引的更多详细信息。对于 Gradle，请参阅Gradle 插件文档的[打包分层 jar 或 war 部分。](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/htmlsingle/#packaging-layered-archives)

#### 12.2. Dockerfile

虽然只需在 Dockerfile 中添加几行代码就可以将 Spring Boot uber jar 转换为 docker 镜像，但我们将使用[分层功能](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#container-images.efficient-images.layering)来创建优化的 docker 镜像。当您创建包含图层索引文件的 jar 时，`spring-boot-jarmode-layertools`jar 将作为依赖项添加到您的 jar 中。将此 jar 放在类路径中，您可以在特殊模式下启动应用程序，该模式允许引导代码运行与应用程序完全不同的东西，例如提取层的东西。

> `Layertools` 模式不能与包含启动脚本的完全可执行的 Spring Boot 存档一起使用。构建打算与 `Layertools`一起使用的 jar 文件时，禁用启动脚本配置。

以下是如何使用`layertools` jar 模式启动 jar 的方法：

```
$ java -Djarmode=layertools -jar my-app.jar
```

这将提供以下输出：

```
Usage:
  java -Djarmode=layertools -jar my-app.jar
​
Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command
```

`extract`命令可用于轻松地将应用程序拆分为多个层以添加到 dockerfile 中。以下是使用 `jarmode`的 Dockerfile 示例。

```
FROM eclipse-temurin:17-jre as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
​
FROM eclipse-temurin:17-jre
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
```

假设以上`Dockerfile`内容位于当前目录中，则可以使用`docker build .` 构建 docker 映像，或者可以选择指定应用程序 jar 的路径，如以下示例所示：

```
$ docker build --build-arg JAR_FILE=path/to/myapp.jar .
```

这是一个多阶段的 dockerfile。构建器阶段提取稍后需要的目录。每个`COPY`命令都与 jarmode 提取的层相关。

当然，不使用jarmode也可以编写Dockerfile。您可以使用`unzip`和`mv`的某种组合将内容移动到正确的层，但 jarmode 简化了这一点。

#### 12.3. 云原生构建包

Dockerfile 只是构建 Docker 镜像的一种方式。构建 docker 镜像的另一种方法是使用 buildpacks 直接从 Maven 或 Gradle 插件。如果您曾经使用过 Cloud Foundry 或 Heroku 等应用程序平台，那么您可能使用过构建包。Buildpack 是平台的一部分，它接受您的应用程序并将其转换为平台可以实际运行的内容。例如，Cloud Foundry 的 Java buildpack 会注意到您正在推送`.jar`文件并自动添加相关的 JRE。

借助 Cloud Native Buildpack，您可以创建可在任何地方运行的 Docker 兼容映像。Spring Boot 包括直接对 Maven 和 Gradle 的构建包支持。这意味着您只需键入一个命令，即可快速将合理的镜像添加到本地运行的 Docker 守护进程中。

[请参阅有关如何将构建包与Maven](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/htmlsingle/#build-image)和[Gradle](https://docs.spring.io/spring-boot/docs/3.2.0/gradle-plugin/reference/htmlsingle/#build-image)结合使用的各个插件文档。

> Paketo [Spring Boot 构建包](https://github.com/paketo-buildpacks/spring-boot)支持`layers.idx`文件，因此应用于该文件的任何自定义都将反映在该构建包创建的映像中。
>
> 为了实现可重复的构建和容器映像缓存，Buildpacks 可以操作应用程序资源元数据（例如文件“上次修改”信息）。您应该确保您的应用程序在运行时不依赖该元数据。Spring Boot 在提供静态资源时可以使用该信息，但可以使用`spring.web.resources.cache.use-last-modified`.

#### 12.4. 接下来读什么

一旦您了解了如何构建高效的容器映像，您就可以阅读有关[将应用程序部署到云平台](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#deployment.cloud.kubernetes)（例如 Kubernetes）的信息。

<br>


# Spring核心功能

原文地址：https\://docs.spring.io/spring-framework/reference/core.html

如果你想参与到[spring framework](https://docs.spring.io/spring-framework/reference/core.html)的翻译中，请到主站联系我，或者点击贡献项目提出issue并star！


# 1.IOC容器和Bean简介

#### 1.1. Spring IoC 容器和 Bean 简介

本章介绍了控制反转 (IoC) 原则的 Spring Framework 实现。IoC 也称为依赖注入 (DI)。这是一个过程，对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖关系（即与它们一起工作的其他对象） . 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖关系的实例化或位置的逆过程（因此称为控制反转）。

`org.springframework.beans`和`org.springframework.context`包是 Spring Framework 的 IoC 容器的基础。 [`BeanFactory`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/BeanFactory.html) 接口提供了一种高级配置机制，能够管理任何类型的对象。 [`ApplicationContext`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/ApplicationContext.html) 是`BeanFactory`的子接口。它增加了如下拓展：

* 更容易与 Spring 的 AOP 功能集成
* 消息资源处理（用于国际化）
* 事件发布
* 应用层特定上下文，例如用于 Web 应用程序的上下文`WebApplicationContext`。

简而言之，`BeanFactory`提供了配置框架和基本功能，而`ApplicationContext`增加了更多的企业特定功能。`ApplicationContext`是`BeanFactory`的完整超集，并且在本章中专门用于描述 Spring 的 IoC 容器。有关使用`BeanFactory`代替`ApplicationContext`更多信息，请参阅涵盖 [`BeanFactory`API](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beanfactory)的部分。

在 Spring 中，构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。否则，bean 只是应用程序中的众多对象之一。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。

####

####

####

#### 1.5. Bean作用域

当您创建一个 bean 定义时，您创建了一个用于创建由该 bean 定义定义的类的实际实例的方法。bean 定义是一个配方的想法很重要，因为这意味着，与一个类一样，您可以从一个配方创建许多对象实例。

您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值，还可以控制从特定 bean 定义创建的对象的作用域。这种方法功能强大且灵活，因为您可以通过配置选择您创建的对象的作用域，而不必在 Java 类级别烘焙对象的作用域。可以将 Bean 定义为部署在多个作用域之一中。Spring 框架支持六个作用域，其中四个仅在您使用 web-aware `ApplicationContext`时可用。您还可以创建 [自定义作用域。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-custom)

下表描述了支持的作用域：

| 作用域                                                                                                                           | 描述                                                                                                                             |
| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| [singleton](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-singleton)     | （默认）将单个 bean 定义限定为每个 Spring IoC 容器的单个对象实例。                                                                                     |
| [prototype](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-prototype)     | 将单个 bean 定义限定为任意数量的对象实例。                                                                                                       |
| [request](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-request)         | 将单个 bean 定义限定为单个 HTTP 请求的生命周期。也就是说，每个 HTTP 请求都有自己的 bean 实例，该实例是在单个 bean 定义的后面创建的。仅在 Web 感知 Spring 的上下文中有效`ApplicationContext`。 |
| [session](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-session)         | 将单个 bean 定义限定为 HTTP 的生命周期`Session`。仅在 Web 感知 Spring 的上下文中有效`ApplicationContext`。                                               |
| [application](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-application) | 将单个 bean 定义限定为`ServletContext`. 仅在 Web 感知 Spring 的上下文中有效`ApplicationContext`。                                                  |
| [application](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp-websocket-scope)   | 将单个 bean 定义限定为`WebSocket`. 仅在 Web 感知 Spring 的上下文中有效`ApplicationContext`。                                                       |

从 Spring 3.0 开始，线程作用域可用，但默认情况下未注册。有关详细信息，请参阅 [`SimpleThreadScope`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/support/SimpleThreadScope.html). 有关如何注册此或任何其他自定义作用域的说明，请参阅 [使用自定义作用域](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-custom-using)。

**1.5.1. 单例作用域**

只有一个单例 bean 的共享实例被管理，并且所有对具有与该 bean 定义匹配的一个或多个 ID 的 bean 的请求都会导致 Spring 容器返回一个特定的 bean 实例。

换句话说，当您定义 bean 定义并将其限定为单例时，Spring IoC 容器会创建该 bean 定义所定义的对象的一个实例。此单个实例存储在此类单例 bean 的缓存中，并且该命名 bean 的所有后续请求和引用都返回缓存的对象。下图显示了单例作用域的工作原理：

![单身人士](https://docs.spring.io/spring-framework/reference/_images/singleton.png)

Spring 的单例 bean 概念不同于设计模式 (GoF) 模式书中定义的单例模式。GoF 单例对对象的作用域进行硬编码，以便每个 ClassLoader 创建一个且仅一个特定类的实例。Spring 单例的作用域最好描述为每个容器和每个 bean。这意味着，如果您在单个 Spring 容器中为特定类定义一个 bean，则 Spring 容器会创建该 bean 定义所定义的类的一个且仅一个实例。单例作用域是 Spring 中的默认作用域。要将 bean 定义为 XML 中的单例，您可以定义一个 bean，如下例所示：

```xml
<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
```

**1.5.2. 原型作用域**

bean 部署的非单例原型作用域导致每次对特定 bean 发出请求时都会创建一个新的 bean 实例。也就是说，将 bean 注入到另一个 bean 中，或者您通过`getBean()`容器上的方法调用来请求它。通常，您应该对所有有状态 bean 使用原型作用域，对无状态 bean 使用单例作用域。

下图说明了 Spring 原型作用域：

![原型](https://docs.spring.io/spring-framework/reference/_images/prototype.png)

（数据访问对象 (DAO) 通常不配置为原型，因为典型的 DAO 不保存任何会话状态。我们更容易重用单例图的核心。）

以下示例将 bean 定义为 XML 中的原型：

```xml
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
```

与其他作用域相比，Spring 不管理原型 bean 的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其交给客户端，而没有进一步记录该原型实例。因此，尽管在所有对象上调用初始化生命周期回调方法而不考虑作用域，但在原型的情况下，不会调用配置的销毁生命周期回调。客户端代码必须清理原型作用域的对象并释放原型 bean 拥有的昂贵资源。要让 Spring 容器释放原型作用域 bean 持有的资源，请尝试使用自定义[bean 后处理器](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-bpp)，它包含对需要清理的 bean 的引用。

在某些方面，Spring 容器在原型作用域 bean 方面的角色是 Java`new`运算符的替代品。此后的所有生命周期管理都必须由客户处理。（有关 Spring 容器中 bean 的生命周期的详细信息，请参阅[Lifecycle Callbacks](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle)。）

**1.5.3. 具有原型 bean 依赖关系的单例 bean**

当您使用具有原型 bean 依赖关系的单例作用域 bean 时，请注意依赖关系在实例化时解决。因此，如果您将原型作用域的 bean 依赖注入到单例作用域的 bean 中，则会实例化一个新的原型 bean，然后将依赖注入到单例 bean 中。原型实例是提供给单例作用域 bean 的唯一实例。

但是，假设您希望单例作用域的 bean 在运行时重复获取原型作用域的 bean 的新实例。您不能将原型作用域的 bean 依赖注入到单例 bean 中，因为该注入仅发生一次，当 Spring 容器实例化单例 bean 并解析并注入其依赖项时。如果您在运行时多次需要原型 bean 的新实例，请参阅[方法注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-method-injection)。

**1.5.4. 请求、会话、应用程序和 WebSocket 作用域**

`request`、`session`、`application`和`websocket`作用域仅在您使用可Web `ApplicationContext`的 Spring实现（例如`XmlWebApplicationContext` ）时才可用。如果您将这些作用域与常规 Spring IoC 容器（例如`ClassPathXmlApplicationContext` ）一起使用，则会抛出一个抱怨未知 bean 作用域的`IllegalStateException`问题。

**初始 Web 配置**

为了支持`request`、`session`、`application`和 `websocket`级别的 bean 作用域（网络作用域的 bean），在定义 bean 之前需要进行一些小的初始配置。（标准作用域不需要此初始设置：`singleton`和`prototype`。）

如何完成此初始设置取决于您的特定 Servlet 环境。

如果您在 Spring Web MVC 中访问作用域 bean，实际上，在 Spring 处理的请求中`DispatcherServlet`，不需要特殊设置。 `DispatcherServlet`已经暴露了所有相关状态。

如果您使用 Servlet 2.5 Web 容器，请求在 Spring 之外处理 `DispatcherServlet`（例如，当使用 JSF 或 Struts 时），您需要注册 `org.springframework.web.context.request.RequestContextListener` `ServletRequestListener`. 对于 Servlet 3.0+，这可以通过使用`WebApplicationInitializer` 接口以编程方式完成。或者，或者对于较旧的容器，将以下声明添加到您的 Web 应用程序的`web.xml`文件中：

```xml
<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>
```

或者，如果您的侦听器设置存在问题，请考虑使用 Spring 的 `RequestContextFilter`. 过滤器映射取决于周围的 Web 应用程序配置，因此您必须根据需要进行更改。以下清单显示了 Web 应用程序的过滤器部分：

```xml
<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>
```

`DispatcherServlet`, `RequestContextListener`, 和`RequestContextFilter`都做完全相同的事情，即将 HTTP 请求对象绑定到为该`Thread`请求提供服务的对象。这使得请求和会话作用域的 bean 在调用链的下游可用。

**请求作用域**

考虑以下 bean 定义的 XML 配置：

```xml
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
```

Spring 容器通过为每个 HTTP 请求使用`LoginAction` bean 定义来创建`loginAction` bean 的新实例。也就是说， `loginAction`bean 的作用域是 HTTP 请求级别。您可以根据需要更改创建的实例的内部状态，因为从同一`loginAction`bean 定义创建的其他实例看不到这些状态更改。它们是针对个人要求的。当请求完成处理时，该请求作用域内的 bean 将被丢弃。

当使用注解驱动的组件或 Java 配置时，`@RequestScope`注解可用于将组件分配给`request`作用域。以下示例显示了如何执行此操作：

```java
@RequestScope
@Component
public class LoginAction {
    // ...
}
```

**会话作用域**

考虑以下 bean 定义的 XML 配置：

```xml
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
```

Spring 容器通过在单个 HTTP 会话的生命周期内使用 `userPreferences bean`定义来创建 `UserPreferences bean` 的新实例。换句话说，`userPreferences bean` 的有效范围是 HTTP 会话级别。与请求范围的 bean 一样，您可以根据需要更改所创建的实例的内部状态，因为您知道也使用从同一 `userPreferences bean` 定义创建的实例的其他 HTTP Session 实例看不到这些状态更改，因为它们特定于单个 HTTP 会话。当 HTTP 会话最终被丢弃时，作用域为该特定 HTTP 会话的 bean 也会被丢弃。

在使用注解驱动的组件或 Java 配置时，您可以使用 `@SessionScope`注解将组件分配给`session`作用域。

```java
@SessionScope
@Component
public class UserPreferences {
    // ...
}
```

**适用作用域**

考虑以下 bean 定义的 XML 配置：

```xml
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
```

Spring 容器通过为整个 Web 应用程序使用一次`AppPreferences` bean 定义来创建`appPreferences` bean 的新实例。也就是说， `appPreferences`bean 是在`ServletContext`级别上限定的，并存储为常规 `ServletContext`属性。这有点类似于 Spring 单例 bean，但在两个重要方面有所不同：它是每个`ServletContext`的单例，而不是 Spring `ApplicationContext`的单例（在任何给定的 Web 应用程序中可能有多个单例），并且它实际上是公开的，因此作为`ServletContext`属性可见.

在使用注解驱动的组件或 Java 配置时，您可以使用 `@ApplicationScope`注解将组件分配给`application`作用域。以下示例显示了如何执行此操作：

```java
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
```

**WebSocket 作用域**

WebSocket 作用域与 WebSocket 会话的生命周期相关联，适用于 STOMP over WebSocket 应用程序，请参阅 [WebSocket 作用域](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp-websocket-scope)了解更多详细信息。

**作用域 Bean 作为依赖项**

Spring IoC 容器不仅管理对象（bean）的实例化，还管理协作者（或依赖项）的连接。如果你想（例如）将一个 HTTP 请求作用域的 bean 注入到另一个具有更长生命周期的 bean 中，你可以选择注入一个 AOP 代理来代替这个作用域 bean。也就是说，您需要注入一个代理对象，该对象公开与作用域对象相同的公共接口，但也可以从相关作用域（例如 HTTP 请求）检索真实目标对象，并将方法调用委托给真实对象。

您还可以在作用域为`singleton` 的 bean 之间使用`<aop:scoped-proxy/>`，然后引用通过可序列化的中间代理，因此能够在反序列化时重新获取目标单例 bean。

当针对作用域的 bean声明时`prototype`使用`<aop:scoped-proxy/>`，共享代理上的每个方法调用都会导致创建一个新的目标实例，然后将调用转发到该实例。

此外，作用域代理并不是以生命周期安全的方式从较短的作用域访问 bean 的唯一方法。您还可以将您的注入点（即构造函数或 setter 参数或自动装配字段）声明为`ObjectFactory<MyTargetBean>`，从而允许`getObject()`在每次需要时调用以按需检索当前实例 - 无需保留实例或单独存储它。

作为扩展变体，您可以为`ObjectProvider<MyTargetBean>` 声明提供几个额外的访问变体，包括`getIfAvailable`和`getIfUnique`。调用它的 JSR-330 变体，`Provider`并与`Provider<MyTargetBean>` 声明和`get()`每次检索尝试的相应调用一起使用。有关整体 JSR-330 的更多详细信息，请参见[此处](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-standard-annotations)。

以下示例中的配置只有一行，但重要的是要了解其背后的“为什么”以及“如何”：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> 
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>
```

要创建这样的代理，请将子`<aop:scoped-proxy/>`元素插入到作用域 bean 定义中（请参阅[选择要创建的代理类型](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other-injection-proxies)和 [基于 XML 模式的配置](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#xsd-schemas)）。为什么在`request`,`session`和自定义作用域级别的 bean 定义需要该`<aop:scoped-proxy/>`元素？考虑以下单例 bean 定义并将其与您需要为上述作用域定义的内容进行对比（请注意，以下 `userPreferences`bean 定义是不完整的）：

```xml
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
```

在前面的示例中，单例 bean ( `userManager`) 被注入了对 HTTP`Session`作用域 bean ( `userPreferences`) 的引用。这里的重点是 `userManager`bean 是一个单例：每个容器只实例化一次，并且它的依赖项（在本例中只有一个，`userPreferences`bean）也只注入一次。这意味着`userManager`bean 仅对完全相同的`userPreferences`对象（即最初注入的对象）进行操作。

当将较短生命周期的作用域 bean 注入较长生命周期的作用域 bean 时，这不是您想要的行为（例如，将 HTTP 会话作用域协作 bean 作为依赖项注入到单例 bean 中）。相反，您需要一个 `userManager` 对象，并且在 HTTP 会话的生命周期内，您需要一个特定于 HTTP 会话的 `userPreferences` 对象。因此，容器创建一个公开与 `UserPreferences` 类完全相同的公共接口的对象（最好是一个 `UserPreferences` 实例的对象），该对象可以从作用域机制（HTTP 请求、Session 等）获取真正的 `UserPreferences` 对象。容器将此代理对象注入到 `userManager` bean 中，而 `userManager` bean 并不知道此 `UserPreferences` 引用是一个代理。在此示例中，当 `UserManager` 实例调用依赖注入的 `UserPreferences` 对象上的方法时，它实际上是调用代理上的方法。然后，代理从（在本例中）HTTP 会话获取真实的\`UserPreferences 对象，并将方法调用委托给检索到的真实 UserPreferences 对象。

因此，在将`request-`和 `session-scoped` bean 注入协作对象时，您需要以下（正确且完整的）配置，如以下示例所示：

```xml
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
```

**选择要创建的代理类型**

默认情况下，当 Spring 容器为使用`<aop:scoped-proxy/>`元素标记的 bean 创建代理时，会创建基于 CGLIB 的类代理。

CGLIB 代理只拦截公共方法调用！不要在此类代理上调用非公共方法。它们没有委托给实际作用域的目标对象。

或者，您可以配置 Spring 容器，通过指定元素`<aop:scoped-proxy/>`的`proxy-target-class`属性为`false`，为此类作用域 bean 创建基于标准 JDK 接口的代理。使用基于 JDK 接口的代理意味着您不需要应用程序类路径中的其他库来影响此类代理。但是，这也意味着作用域 bean 的类必须实现至少一个接口，并且注入作用域 bean 的所有协作者都必须通过其接口之一引用该 bean。以下示例显示了基于接口的代理：

```xml
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
```

有关选择基于类或基于接口的代理的更多详细信息，请参阅[代理机制](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-proxying)。

**1.5.5. 自定义作用域**

bean 作用域机制是可扩展的。您可以定义自己的作用域，甚至重新定义现有作用域，尽管后者被认为是不好的做法，并且您不能覆盖内置`singleton`和`prototype`作用域。

**创建自定义作用域**

要将您的自定义作用域集成到 Spring 容器中，您需要实现 `org.springframework.beans.factory.config.Scope`接口，这将在本节中描述。有关如何实现自己的作用域的想法，请参阅Spring `Scope` 框架本身和 [`Scope`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/config/Scope.html)javadoc 提供的实现，其中更详细地解释了您需要实现的方法。

`Scope`接口有四种方法可以从作用域中获取对象，将它们从作用域中移除，并让它们被销毁。

例如，会话作用域实现返回会话作用域的 bean（如果它不存在，则该方法在将其绑定到会话以供将来参考之后返回 bean 的新实例）。以下方法从底层作用域返回对象：

```java
Object get(String name, ObjectFactory<?> objectFactory)
```

例如，会话作用域实现从底层会话中删除会话作用域 bean。该对象应该被返回，但是`null`如果没有找到具有指定名称的对象，您可以返回。以下方法从底层作用域中删除对象：

```java
Object remove(String name)
```

以下方法注册了一个回调，当作用域被销毁或作用域中的指定对象被销毁时应调用该回调：

```java
void registerDestructionCallback(String name, Runnable destructionCallback)
```

有关销毁回调的更多信息，请参阅[javadoc](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/config/Scope.html#registerDestructionCallback) 或 Spring 作用域实现。

以下方法获取基础作用域的对话标识符：

```java
String getConversationId()
```

这个标识符对于每个作用域都是不同的。对于会话作用域的实现，此标识符可以是会话标识符。

**使用自定义作用域**

在编写和测试一个或多个自定义`Scope`实现之后，您需要让 Spring 容器知道您的新作用域。以下方法是向Spring `Scope` 容器注册新的中心方法：

```java
void registerScope(String scopeName, Scope scope);
```

此方法在`ConfigurableBeanFactory`接口上声明，可通过 Spring 附带的大多数具体`ApplicationContext`实现的`BeanFactory`属性获得。

`registerScope(..)`方法的第一个参数是与作用域关联的唯一名称。Spring 容器本身中此类名称的示例是`singleton`和 `prototype`。`registerScope(..)`方法的第二个参数是您希望注册和使用的自定义`Scope`实现的实际实例。

假设您编写了自定义`Scope`实现，然后按照下一个示例所示进行注册。

下一个示例使用`SimpleThreadScope`，它包含在 Spring 中，但默认情况下未注册。对于您自己的自定义`Scope` 实现，说明将是相同的。

```java
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
```

然后，您可以创建符合您的自定义作用域`Scope`规则的 bean 定义， 如下所示：

```xml
<bean id="..." class="..." scope="thread">
```

使用自定义`Scope`实现，您不仅限于作用域的编程注册。您还可以`Scope`使用 `CustomScopeConfigurer`类以声明方式进行注册，如以下示例所示：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>
```

当您在实现的 `FactoryBean`的`<bean>`声明中放置`<aop:scoped-proxy/>`时，作用域是工厂 bean 本身，而不是从`getObject()`.

#### 1.6. 自定义 Bean 的性质

Spring Framework 提供了许多接口，您可以使用它们来自定义 bean 的性质。本节将它们分组如下：

* [生命周期回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle)
* [`ApplicationContextAware`和`BeanNameAware`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware)
* [其他`Aware`接口](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aware-list)

**1.6.1. 生命周期回调**

要与容器对 bean 生命周期的管理进行交互，可以实现 Spring`InitializingBean`和`DisposableBean`接口。容器调用了 `afterPropertiesSet()`方法和`destroy()`方法让 bean 在初始化和销毁 bean 时执行某些操作。

JSR-250`@PostConstruct`和`@PreDestroy`注解通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 不会耦合到 Spring 特定的接口。有关详细信息，请参阅[使用`@PostConstruct`和`@PreDestroy`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-postconstruct-and-predestroy-annotations)。如果您不想使用 JSR-250 注解但仍想移除耦合，请考虑使用`init-method`和`destroy-method`bean 定义元数据。

在内部，Spring 框架使用`BeanPostProcessor`实现来处理它可以找到的任何回调接口并调用适当的方法。如果你需要自定义特性或其他生命周期行为 Spring 默认不提供，你可以自己实现一个`BeanPostProcessor`。有关详细信息，请参阅 [容器扩展点](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension)。

除了初始化和销毁回调之外，Spring 管理的对象还可以实现`Lifecycle`接口，以便这些对象可以参与容器自身生命周期驱动的启动和关闭过程。

本节介绍生命周期回调接口。

**初始化回调**

在容器为 bean 设置了所有必要的属性后，`org.springframework.beans.factory.InitializingBean`接口允许 bean 执行初始化工作。`InitializingBean`接口指定了一个方法：

```java
void afterPropertiesSet() throws Exception;
```

我们建议您不要使用`InitializingBean`接口，因为它不必要地将代码耦合到 Spring。或者，我们建议使用[`@PostConstruct`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-postconstruct-and-predestroy-annotations)注解或指定 POJO 初始化方法。在基于 XML 的配置元数据的情况下，您可以使用`init-method`属性来指定具有无效无参数签名的方法的名称。通过 Java 配置，您可以使用 `@Bean`的`initMethod`属性. 请参阅[接收生命周期回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-lifecycle-callbacks)。考虑以下示例：

```xml
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
```

```java
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}
```

前面的示例与下面的示例（由两个列表组成）具有几乎完全相同的效果：

```xml
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
```

```java
public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
```

但是，前面两个示例中的第一个没有将代码耦合到 Spring。

**销毁回调**

实现该`org.springframework.beans.factory.DisposableBean`接口可以让 bean 在包含它的容器被销毁时获得回调。 `DisposableBean`接口指定了一个方法：

```java
void destroy() throws Exception;
```

我们建议您不要使用`DisposableBean`回调接口，因为它不必要地将代码耦合到 Spring。或者，我们建议使用[`@PreDestroy`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-postconstruct-and-predestroy-annotations)注解或指定 bean 定义支持的通用方法。使用基于 XML 的配置元数据，您可以使用`<bean/>`的`destroy-method`属性. 通过 Java 配置，您可以使用`@Bean`的`destroyMethod`属性. 请参阅 [接收生命周期回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-lifecycle-callbacks)。考虑以下定义：

```xml
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
```

```java
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}
```

前面的定义与下面的定义几乎完全相同：

```xml
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
```

```java
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
```

但是，前面两个定义中的第一个没有将代码耦合到 Spring。

您可以为 `<bean>`元素的 `destroy-method` 属性分配一个特殊的`(inferred)`值，该值指示 Spring 自动检测特定 bean 类上的公共 `close` 或 `shutdown` 方法。 （因此，任何实现 `java.lang.AutoCloseable` 或 `java.io.Closeable` 的类都会匹配。）您还可以在`<beans>` 元素的 `default-destroy-method` 属性上设置此特殊`(inferred)`值，以将此行为应用于一整套 bean（请参阅默认初始化和销毁方法）。请注意，这是 Java 配置的默认行为。

**默认初始化和销毁方法**

当您编写不使用 Spring 特定`InitializingBean`和`DisposableBean`回调接口的初始化和销毁方法回调时，您通常会编写名称为`init()`、`initialize()`、`dispose()`等的方法。理想情况下，此类生命周期回调方法的名称在整个项目中是标准化的，以便所有开发人员使用相同的方法名称并确保一致性。

您可以将 Spring 容器配置为“查找”命名初始化并销毁每个 bean 上的回调方法名称。这意味着，作为应用程序开发人员，您可以编写应用程序类并使用名为`init()` 的初始化回调 ，而无需为`init-method="init"`每个 bean 定义配置属性。Spring IoC 容器在创建 bean 时调用该方法（并根据[前面描述](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle)的标准生命周期回调协定）。此功能还为初始化和销毁方法回调强制执行一致的命名约定。

假设您的初始化回调方法已命名为`init()`，而您的销毁回调方法已命名为`destroy()`。然后，您的类类似于以下示例中的类：

```java
public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}
```

然后，您可以在类似于以下内容的 bean 中使用该类：

```xml
<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>
```

顶级`<beans/>`元素属性上的属性`default-init-method`的存在导致 Spring IoC 容器将 bean 类上调用的`init`方法识别为初始化方法回调。当创建和组装一个 bean 时，如果 bean 类有这样的方法，它会在适当的时候被调用。

您可以使用顶级`<beans/>`元素上的属性`default-destroy-method`类似地配置销毁方法回调（即在 XML 中） 。

如果现有的 bean 类已经具有命名与约定不一致的回调方法，您可以通过使用`<bean/>` 自身的`init-method`和`destroy-method`属性指定（在 XML 中）方法名称来覆盖默认值。

Spring 容器保证在为 bean 提供所有依赖项后立即调用配置的初始化回调。因此，在原始 bean 引用上调用初始化回调，这意味着 AOP 拦截器等尚未应用于 bean。首先完全创建一个目标 bean，然后应用一个 AOP 代理（例如）及其拦截器链。如果目标 bean 和代理是分开定义的，您的代码甚至可以绕过代理与原始目标 bean 交互。因此，将拦截器应用于该`init`方法将是不一致的，因为这样做会将目标 bean 的生命周期与其代理或拦截器耦合，并在您的代码直接与原始目标 bean 交互时留下奇怪的语义。

**结合生命周期机制**

从 Spring 2.5 开始，您可以通过三个选项来控制 bean 生命周期行为：

* 和[`InitializingBean`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean)回调 [`DisposableBean`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-disposablebean)接口
* 自定义`init()`和`destroy()`方法
* [`@PostConstruct`和`@PreDestroy` 注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-postconstruct-and-predestroy-annotations)。\_ 您可以结合这些机制来控制给定的 bean。

如果为一个 bean 配置了多个生命周期机制，并且每个机制都配置了不同的方法名称，那么每个配置的方法都按照本注解后列出的顺序运行。`init()`但是，如果为多个生命周期机制 配置了相同的方法名称（例如， 对于初始化方法），则该方法将运行一次，如上[一节所述](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-default-init-destroy-methods)。

为同一个bean配置的多个生命周期机制，不同的初始化方法，调用如下：

1. 用注解的方法`@PostConstruct`
2. `afterPropertiesSet()`由`InitializingBean`回调接口定义
3. 自定义配置`init()`方法

销毁方法的调用顺序相同：

1. 用注解的方法`@PreDestroy`
2. `destroy()`由`DisposableBean`回调接口定义
3. 自定义配置`destroy()`方法

**启动和关闭回调**

`Lifecycle`接口定义了任何具有自己生命周期要求的对象的基本方法（例如启动和停止某些后台进程）：

```java
public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}
```

任何 Spring 管理的对象都可以实现`Lifecycle`接口。然后，当 `ApplicationContext`自身接收到启动和停止信号时（例如，对于运行时的停止/重新启动场景），它会将这些调用级联到该上下文中定义的所有`Lifecycle`实现。它通过委托给`LifecycleProcessor`来做到这一点，如以下清单所示：

```java
public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}
```

请注意，`LifecycleProcessor`本身就是`Lifecycle` 接口的扩展。它还添加了另外两种方法来对正在刷新和关闭的上下文做出反应。

请注意，常规`org.springframework.context.Lifecycle`接口是显式启动和停止通知的简单约定，并不意味着在上下文刷新时自动启动。要对特定 bean 的自动启动（包括启动阶段）进行细粒度控制，请考虑实施`org.springframework.context.SmartLifecycle`。另外，请注意，停止通知不能保证在销毁之前发出。在常规关闭时，所有`Lifecycle`bean 在传播一般销毁回调之前首先收到停止通知。但是，在上下文的生命周期内进行热刷新或停止刷新尝试时，只会调用销毁方法。

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖”关系，则依赖方在其依赖之后开始，并在其依赖之前停止。但是，有时，直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象开始。在这些情况下，`SmartLifecycle`接口定义了另一个选项`getPhase()`，即在其超接口`Phased`上定义的方法 . 以下清单显示了`Phased`接口的定义：

```java
public interface Phased {

    int getPhase();
}
```

以下清单显示了`SmartLifecycle`接口的定义：

```java
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}
```

启动时，相位最低的对象首先启动。停止时，按照相反的顺序。因此，实现`SmartLifecycle`并且其`getPhase()`方法返回的对象`Integer.MIN_VALUE`将是第一个开始和最后一个停止的对象。在频谱的另一端，相位值 `Integer.MAX_VALUE`表示对象应该最后启动并首先停止（可能是因为它依赖于正在运行的其他进程）。在考虑阶段值时，了解任何未实现`SmartLifecycle`的“正常”`Lifecycle`对象的默认阶段为`0`也很重要。因此，任何负相位值都表示对象应该在这些标准组件之前开始（并在它们之后停止）。对于任何正相位值，反之亦然。

`SmartLifecycle`定义的 stop 方法接受一个回调。在该实现的关闭过程完成后，任何实现都必须调用该回调的`run()`方法。这会在必要时启用异步关闭，因为接口`LifecycleProcessor` 的默认实现`DefaultLifecycleProcessor`，等待每个阶段内的对象组调用该回调的超时值。默认的每阶段超时为 30 秒。您可以通过在上下文中定义一个命名的`lifecycleProcessor` bean 来覆盖默认的生命周期处理器实例 。如果您只想修改超时，定义以下内容就足够了：

```xml
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
```

如前所述，该`LifecycleProcessor`接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程，就好像`stop()`已被显式调用一样，但它发生在上下文关闭时。另一方面，“刷新”回调启用了 `SmartLifecycle`bean 的另一个特性。刷新上下文时（在所有对象都已实例化和初始化之后），将调用该回调。此时，默认生命周期处理器会检查每个 `SmartLifecycle`对象的`isAutoStartup()`方法返回的布尔值。如果`true`，则该对象在该点启动，而不是等待上下文或其自身的显式调用`start()`方法（与上下文刷新不同，对于标准上下文实现，上下文启动不会自动发生）。如前所述，`phase`值和任何“依赖”关系决定了启动顺序。

**在非 Web 应用程序中优雅地关闭 Spring IoC 容器**

本节仅适用于非 Web 应用程序。Spring 的基于 Web 的 `ApplicationContext`实现已经有代码可以在相关 Web 应用程序关闭时优雅地关闭 Spring IoC 容器。

如果您在非 Web 应用程序环境中（例如，在富客户端桌面环境中）使用 Spring 的 IoC 容器，请向 JVM 注册一个关闭挂钩。这样做可确保正常关闭并在单例 bean 上调用相关的销毁方法，以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。

要注册关闭挂钩，请调用接口`registerShutdownHook()`上声明的方法`ConfigurableApplicationContext`，如以下示例所示：

```java
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}
```

**1.6.2. `ApplicationContextAware`和`BeanNameAware`**

当 an`ApplicationContext`创建一个实现 `org.springframework.context.ApplicationContextAware`接口的对象实例时，会为该实例提供对该 的引用`ApplicationContext`。以下清单显示了`ApplicationContextAware`接口的定义：

```java
public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
```

因此，bean 可以通过 `ApplicationContext` 接口或通过将引用强制转换为该接口的已知子类（例如 `ConfigurableApplicationContext`，它公开了附加功能），以编程方式操作创建它们的 `ApplicationContext`。一种用途是以编程方式检索其他 bean。有时此功能很有用。然而，一般来说，您应该避免它，因为它将代码耦合到 Spring 并且不遵循控制反转风格，在这种风格中，协作者作为属性提供给 bean。 `ApplicationContext` 的其他方法提供对文件资源的访问、发布应用程序事件以及访问 `MessageSource`。这些附加功能在 `ApplicationContext` 的附加功能中进行了描述。

自动装配是获取 `ApplicationContext` 引用的另一种替代方法。 *传统* `constructor`模式和自动装配`byType`模式（如[Autowiring Collaborators](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire)中所述）可以分别为构造函数参数或 setter 方法参数提供`ApplicationContext`类型依赖 。要获得更大的灵活性，包括自动装配字段和多个参数方法的能力，请使用基于注解的自动装配功能。如果你这样做，如果相关的字段、构造函数或方法带有`@Autowired`注解，则`ApplicationContext`会自动装配到期望`ApplicationContext`类型的字段、构造函数参数或方法参数中。有关详细信息，请参阅 [使用](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation).[`@Autowired`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation)

当创建一个实现 `org.springframework.beans.factory.BeanNameAware`接口的`ApplicationContext`类时，该类被提供了对其关联对象定义中定义的名称的引用。以下清单显示了 BeanNameAware 接口的定义：

```java
public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}
```

在填充普通 bean 属性之后但在初始化回调（例如`InitializingBean.afterPropertiesSet()`自定义 init 方法）之前调用回调。

**1.6.3. 其他`Aware`接口**

除了`ApplicationContextAware`和`BeanNameAware`（前面讨论[过](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware)）之外，Spring 提供了广泛的`Aware`回调接口，让 bean 向容器指示它们需要特定的基础设施依赖项。作为一般规则，名称表示依赖类型。下表总结了最重要的`Aware`接口：

| 名称                               | 注入依赖                                                                | 解释...                                                                                                                                          |
| -------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `ApplicationContextAware`        | 声明`ApplicationContext`.                                             | [`ApplicationContextAware`和`BeanNameAware`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware) |
| `ApplicationEventPublisherAware` | 封闭的事件发布者`ApplicationContext`。                                       | [的附加功能`ApplicationContext`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-introduction)                |
| `BeanClassLoaderAware`           | 类加载器用于加载 bean 类。                                                    | [实例化 Bean](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-class)                                  |
| `BeanFactoryAware`               | 声明`BeanFactory`.                                                    | [`BeanFactory`API \_](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beanfactory)                         |
| `BeanNameAware`                  | 声明 bean 的名称。                                                        | [`ApplicationContextAware`和`BeanNameAware`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware) |
| `LoadTimeWeaverAware`            | 定义的编织器，用于在加载时处理类定义。                                                 | [在 Spring 框架中使用 AspectJ 进行加载时编织](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-aj-ltw)                     |
| `MessageSourceAware`             | 用于解析消息的配置策略（支持参数化和国际化）。                                             | [的附加功能`ApplicationContext`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-introduction)                |
| `NotificationPublisherAware`     | Spring JMX 通知发布者。                                                   | [通知](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#jmx-notifications)                                   |
| `ResourceLoaderAware`            | 为对资源进行低级访问而配置的加载程序。                                                 | [资源](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources)                                                  |
| `ServletConfigAware`             | 当前`ServletConfig`容器在其中运行。仅在可感知网络的 Spring 中有效 `ApplicationContext`。  | [spring MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc)                                                 |
| `ServletContextAware`            | 当前`ServletContext`容器在其中运行。仅在可感知网络的 Spring 中有效 `ApplicationContext`。 | [spring MVC](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc)                                                 |

再次注意，使用这些接口将您的代码绑定到 Spring API，并且不遵循 Inversion of Control 样式。因此，我们建议将它们用于需要以编程方式访问容器的基础设施 bean。

#### 1.7. Bean定义继承

一个 bean 定义可以包含很多配置信息，包括构造函数参数、属性值和特定于容器的信息，例如初始化方法、静态工厂方法名称等。子 bean 定义从父定义继承配置数据。子定义可以根据需要覆盖某些值或添加其他值。使用父子bean定义可以节省大量输入。实际上，这是一种模板形式。

如果您以编程方式使用接口`ApplicationContext`，则子 bean 定义由`ChildBeanDefinition`类表示。大多数用户不在此级别上与他们合作。相反，他们在诸如`ClassPathXmlApplicationContext`. 当您使用基于 XML 的配置元数据时，您可以通过使用属性来指示子 bean 定义`parent`，将父 bean 指定为该属性的值。以下示例显示了如何执行此操作：

```xml
<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
```

如果没有指定子 bean 定义，则使用父定义中的 bean 类，但也可以覆盖它。在后一种情况下，子 bean 类必须与父类兼容（即，它必须接受父类的属性值）。

子 bean 定义从父 bean 继承作用域、构造函数参数值、属性值和方法覆盖，并可选择添加新值。您指定的任何作用域、初始化方法、销毁方法或`static`工厂方法设置都会覆盖相应的父设置。

其余的设置总是取自子定义：依赖、自动装配模式、依赖检查、单例和惰性初始化。

前面的示例通过使用`abstract`属性将父 bean 定义显式标记为抽象。如果父定义未指定类，则将父 bean 定义显式标记`abstract`为必需，如以下示例所示：

```xml
<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
```

父 bean 不能自己实例化，因为它不完整，而且它也显式标记为`abstract`. 当定义为`abstract`时，它只能用作纯模板 bean 定义，用作子定义的父定义。尝试单独使用这样的`abstract`父 bean，通过将其引用为另一个 bean 的 ref 属性或`getBean()`使用父 bean ID 进行显式调用会返回错误。同样，容器的内部 `preInstantiateSingletons()`方法会忽略定义为抽象的 bean 定义。

默认情况下`ApplicationContext`预实例化所有单例。因此，重要的是（至少对于单例 bean），如果您有一个（父）bean 定义，您打算仅将其用作模板，并且此定义指定了一个类，则必须确保将*抽象*属性设置为*true*，否则应用程序上下文将实际（尝试）预实例化`abstract`bean。

#### 1.8. 容器扩展点

通常，应用程序开发人员不需要子类化`ApplicationContext` 实现类。相反，可以通过插入特殊集成接口的实现来扩展 Spring IoC 容器。接下来的几节描述了这些集成接口。

**1.8.1. 通过使用自定义 Bean`BeanPostProcessor`**

`BeanPostProcessor`接口定义了您可以实现的回调方法，以提供您自己的（或覆盖容器的默认）实例化逻辑、依赖关系解析逻辑等。如果你想在 Spring 容器完成实例化、配置和初始化 bean 之后实现一些自定义逻辑，你可以插入一个或多个自定义`BeanPostProcessor`实现。

您可以配置多个`BeanPostProcessor`实例，并且可以通过设置`order`属性来控制这些`BeanPostProcessor`实例的运行顺序。仅当`BeanPostProcessor`实现`Ordered` 接口时才能设置此属性。如果你自己写`BeanPostProcessor`，你也应该考虑实现`Ordered`接口。有关详细信息，请参阅 [`BeanPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html) 和[`Ordered`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/Ordered.html)接口的 javadoc。另请参阅有关[实例的](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-programmatically-registering-beanpostprocessors)[编程注册的`BeanPostProcessor`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-programmatically-registering-beanpostprocessors)说明。

`BeanPostProcessor`实例对 bean（或对象）实例进行操作。也就是说，Spring IoC 容器实例化一个 bean 实例，然后`BeanPostProcessor` 实例完成它们的工作。`BeanPostProcessor`实例的作用域是每个容器。这仅在您使用容器层次结构时才相关。如果您在一个容器中定义`BeanPostProcessor`，它只会对该容器中的 bean 进行后处理。换句话说，在一个容器中定义的 bean 不会被另一个容器中定义的 bean 进行后处理`BeanPostProcessor`，即使两个容器是同一层次结构的一部分。要更改实际的 bean 定义（即定义 bean 的蓝图），您需要使用 a`BeanFactoryPostProcessor`，如 使用[自定义配置元数据中所述`BeanFactoryPostProcessor`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-factory-postprocessors)。

该`org.springframework.beans.factory.config.BeanPostProcessor`接口恰好由两个回调方法组成。当这样的类注册为容器的后处理器时，对于容器创建的每个 bean 实例，后处理器都会在容器初始化方法（例如`InitializingBean.afterPropertiesSet()`或任何声明`init`的方法）之前从容器中获取回调调用，并在任何 bean 初始化回调之后。后处理器可以对 bean 实例采取任何行动，包括完全忽略回调。一个 bean 后处理器通常检查回调接口，或者它可以用代理包装一个 bean。一些 Spring AOP 基础结构类被实现为 bean 后处理器，以提供代理包装逻辑。

自动检测在实现接口`ApplicationContext`的配置元数据中定义的任何 bean 。`ApplicationContext`将 这些 `BeanPostProcessor`bean 注册为后处理器，以便稍后在创建 bean 时调用它们。Bean 后处理器可以以与任何其他 bean 相同的方式部署在容器中。

请注意，当在配置类上使用工厂方法`@Bean`声明 `BeanPostProcessor` 时，工厂方法的返回类型应该是实现类本身或至少是`org.springframework.beans.factory.config.BeanPostProcessor` 接口，清楚地表明该 bean 的后处理器性质。否则，在 `ApplicationContext`完全创建之前无法按类型自动检测它。由于需要尽早实例化`BeanPostProcessor` 以应用于上下文中其他 bean 的初始化，因此这种早期类型检测至关重要。

以编程方式注册`BeanPostProcessor`实例虽然推荐的`BeanPostProcessor`注册方法是通过 `ApplicationContext`自动检测（如前所述），但您可以`ConfigurableBeanFactory`使用该`addBeanPostProcessor` 方法以编程方式注册它们。当您需要在注册之前评估条件逻辑，甚至在层次结构中跨上下文复制 bean 后处理器时，这可能很有用。但是请注意，以`BeanPostProcessor`编程方式添加的实例不遵从`Ordered`接口。在这里，注册的顺序决定了执行的顺序。另请注意，以`BeanPostProcessor`编程方式注册的实例始终在通过自动检测注册的实例之前处理，无论任何显式排序如何。

`BeanPostProcessor`实例和 AOP 自动代理实现`BeanPostProcessor`接口的类是特殊的，被容器区别对待。它们直接引用的所有`BeanPostProcessor`实例和 bean 都在启动时实例化，作为`ApplicationContext`. 接下来，所有`BeanPostProcessor`实例都以排序方式注册并应用于容器中的所有其他 bean。因为 AOP 自动代理是作为`BeanPostProcessor`自身实现的，所以`BeanPostProcessor` 实例和它们直接引用的 bean 都没有资格进行自动代理，因此没有将方面编织到其中。对于任何这样的 bean，您应该会看到一条信息性日志消息：`Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)`.如果您`BeanPostProcessor`使用自动装配或 `@Resource`（可能回退到自动装配）将 bean 连接到您的 bean，则 Spring 在搜索类型匹配依赖项候选时可能会访问意外的 bean，因此，使它们没有资格进行自动代理或其他类型的 bean 发布-加工。例如，如果您有一个依赖项注解，`@Resource`其中字段或 setter 名称不直接对应于 bean 的声明名称并且没有使用 name 属性，则 Spring 会访问其他 bean 以按类型匹配它们。

以下示例展示了如何`BeanPostProcessor`在`ApplicationContext`.

**示例：Hello World, `BeanPostProcessor`-style**

第一个示例说明了基本用法。该示例显示了一个自定义 `BeanPostProcessor`实现，该实现调用`toString()`容器创建的每个 bean 的方法，并将结果字符串打印到系统控制台。

以下清单显示了自定义`BeanPostProcessor`实现类定义：

```java
package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
```

以下`beans`元素使用`InstantiationTracingBeanPostProcessor`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>
```

请注意`InstantiationTracingBeanPostProcessor`是如何定义的。它甚至没有名字，而且，因为它是一个 bean，它可以像任何其他 bean 一样被依赖注入。（前面的配置还定义了一个由 Groovy 脚本支持的 bean。Spring 动态语言支持在“ [动态语言支持](https://docs.spring.io/spring-framework/docs/current/reference/html/languages.html#dynamic-language)”一章中有详细说明。）

以下 Java 应用程序运行上述代码和配置：

```java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}
```

上述应用程序的输出类似于以下内容：

```
Bean“信使”创建：org.springframework.scripting.groovy.GroovyMessenger@272961 
org.springframework.scripting.groovy.GroovyMessenger@272961
```

**示例：`AutowiredAnnotationBeanPostProcessor`**

将回调接口或注解与自定义`BeanPostProcessor` 实现结合使用是扩展 Spring IoC 容器的常用方法。一个例子是 Spring 的`AutowiredAnnotationBeanPostProcessor` ——一个`BeanPostProcessor`随 Spring 发行版一起提供的实现，并自动连接带注解的字段、setter 方法和任意配置方法。

**1.8.2. 自定义配置元数据`BeanFactoryPostProcessor`**

我们要看的下一个扩展点是 `org.springframework.beans.factory.config.BeanFactoryPostProcessor`. 此接口的语义与 的语义相似，但`BeanPostProcessor`有一个主要区别：`BeanFactoryPostProcessor`对 bean 配置元数据进行操作。也就是说，Spring IoC 容器允许`BeanFactoryPostProcessor`读取配置元数据并可能在容器实例化除实例之外的任何 bean*之前*`BeanFactoryPostProcessor`更改它。

您可以配置多个实例，并且可以通过设置属性`BeanFactoryPostProcessor`来控制这些`BeanFactoryPostProcessor`实例的运行顺序。`order`但是，您只能在`BeanFactoryPostProcessor`实现 `Ordered`接口时设置此属性。如果你自己写`BeanFactoryPostProcessor`，你也应该考虑实现`Ordered`接口。[`BeanFactoryPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/config/BeanFactoryPostProcessor.html) 有关更多详细信息，请参阅和[`Ordered`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/Ordered.html)接口的 javadoc 。

如果您想更改实际的 bean 实例（即从配置元数据创建的对象），那么您需要使用 a `BeanPostProcessor` （前面在[使用 a 自定义 Bean`BeanPostProcessor`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-bpp)中进行了描述）。虽然在技术上可以在 a 中使用 bean 实例`BeanFactoryPostProcessor`（例如，通过使用 `BeanFactory.getBean()`），但这样做会导致 bean 过早实例化，从而违反标准容器生命周期。这可能会导致负面影响，例如绕过 bean 后处理。此外，`BeanFactoryPostProcessor`实例的作用域是每个容器。这仅在您使用容器层次结构时才相关。如果您在一个容器中定义 `BeanFactoryPostProcessor`，它仅适用于该容器中的 bean 定义。一个容器中的 Bean 定义不会由另一个容器中的`BeanFactoryPostProcessor`实例进行后处理，即使两个容器都属于同一层次结构。

bean 工厂后处理器在 `ApplicationContext` 中声明时会自动运行，以便将更改应用于定义容器的配置元数据。Spring 包括许多预定义的 bean factory 后处理器，例如`PropertyOverrideConfigurer`和 `PropertySourcesPlaceholderConfigurer`. 您还可以使用自定义`BeanFactoryPostProcessor` - 例如，注册自定义属性编辑器。

`ApplicationContext`自动检测部署到其中实现`BeanFactoryPostProcessor`接口的任何 bean。它在适当的时候将这些 bean 用作 bean 工厂后处理器。您可以像部署任何其他 bean 一样部署这些后处理器 bean。

与`BeanPostProcessor`s 一样，您通常不希望将 `BeanFactoryPostProcessor`s 配置为延迟初始化。如果没有其他 bean 引用 `Bean(Factory)PostProcessor`，则该后处理器根本不会被实例化。因此，将其标记为延迟初始化将被忽略，并且 `Bean(Factory)PostProcessor`即使您 在元素`<beans />` 的声明中将`default-lazy-init`属性设置为`true`，也会立即实例化。

**示例：类名替换`PropertySourcesPlaceholderConfigurer`**

您可以使用标准 Java格式`PropertySourcesPlaceholderConfigurer`将 bean 定义中的属性值外部化到单独的`Properties`文件中。这样做使部署应用程序的人员能够自定义特定于环境的属性，例如数据库 URL 和密码，而无需修改容器的主要 XML 定义文件或文件的复杂性或风险。

考虑以下基于 XML 的配置元数据片段，其中 定义了带有占位符值的`DataSource`：

```xml
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
```

该示例显示了从外部`Properties`文件配置的属性。在运行时，将 a`PropertySourcesPlaceholderConfigurer`应用于替换 DataSource 的某些属性的元数据。要替换的值被指定为表单的占位符`${property-name}`，它遵循 Ant 和 log4j 以及 JSP EL 样式。

实际值来自另一个标准 Java`Properties`格式的文件：

```
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
```

因此，该`${jdbc.username}`字符串在运行时被值“sa”替换，同样适用于与属性文件中的键匹配的其他占位符值。检查 bean 定义的大多数属性和属性中的`PropertySourcesPlaceholderConfigurer`占位符。此外，您可以自定义占位符前缀和后缀。

使用Spring 2.5 中引入的命名空间`context`，您可以使用专用配置元素配置属性占位符。您可以在属性中以逗号分隔列表的形式提供一个或多个位置`location`，如以下示例所示：

```xml
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
```

`PropertySourcesPlaceholderConfigurer`不仅在您指定的文件中查找属性`Properties` 。默认情况下，如果在指定的属性文件中找不到属性，它会检查 Spring`Environment`属性和常规 Java`System`属性。

您可以使用`PropertySourcesPlaceholderConfigurer`替换类名，当您必须在运行时选择特定的实现类时，这有时很有用。以下示例显示了如何执行此操作：

```xml
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
	<property name="locations">
		<value>classpath:com/something/strategy.properties</value>
	</property>
	<property name="properties">
		<value>custom.strategy.class=com.something.DefaultStrategy</value>
	</property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>
```

如果该类在运行时无法解析为有效类，则该 bean 在即将创建时解析失败，这是在`ApplicationContext` 非惰性初始化 bean的`preInstantiateSingletons()` 阶段。

**示例：`PropertyOverrideConfigurer`**

另一个 bean 工厂后处理器`PropertyOverrideConfigurer`，类似于`PropertySourcesPlaceholderConfigurer` ，但与后者不同的是，原始定义可以具有默认值或根本没有 bean 属性的值。如果覆盖 `Properties`文件没有特定 bean 属性的条目，则使用默认上下文定义。

请注意，bean 定义不知道被覆盖，因此从 XML 定义文件中不能立即看出正在使用覆盖配置器。如果有多个`PropertyOverrideConfigurer`实例为同一个 bean 属性定义不同的值，由于覆盖机制，最后一个会获胜。

属性文件配置行采用以下格式：

```
beanName.property=值
```

以下清单显示了格式的示例：

```
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
```

这个示例文件可以与一个容器定义一起使用，该容器定义包含一个名为的 bean ，该 `dataSource`bean具有`driver`和`url`属性。

还支持复合属性名称，只要路径的每个组件（除了要覆盖的最终属性）都已经非空（可能由构造函数初始化）。在以下示例中，将 bean `tom`的属性`fred`的属性`bob`的`sammy`属性设置为标量值`123`：

```
tom.fred.bob.sammy=123
```

指定的覆盖值始终是文字值。它们不会被翻译成 bean 引用。当 XML bean 定义中的原始值指定 bean 引用时，该约定也适用。

使用Spring 2.5 中引入的命名空间`context`，可以使用专用配置元素配置属性覆盖，如以下示例所示：

```xml
<context:property-override location="classpath:override.properties"/>
```

**1.8.3. 自定义实例化逻辑`FactoryBean`**

您可以为本身是工厂的对象实现接口`org.springframework.beans.factory.FactoryBean`。

该`FactoryBean`接口是 Spring IoC 容器的实例化逻辑的可插入点。如果您有复杂的初始化代码，用 Java 更好地表达而不是（可能）冗长的 XML，您可以创建自己的 `FactoryBean`，在该类中编写复杂的初始化，然后将您的自定义`FactoryBean`插入容器中。

该`FactoryBean<T>`接口提供了三种方法：

* `T getObject()`：返回此工厂创建的对象的实例。该实例可能会被共享，具体取决于该工厂是返回单例还是原型。
* `boolean isSingleton()`：`true`如果`FactoryBean`返回单例或 `false`其他，则返回。此方法的默认实现返回`true`.
* `Class<?> getObjectType()`：返回`getObject()`方法返回的对象类型，或者`null`如果事先不知道类型。

Spring `FactoryBean`框架中的许多地方都使用了概念和接口。Spring `FactoryBean`本身提供了超过 50 个接口的实现。

当您需要向容器请求实际的 FactoryBean 实例本身而不是它生成的 bean 时，请在调用 ApplicationContext 的 getBean() 方法时在 bean 的 id 前加上与号 (&) 前缀。因此，对于 id 为 myBean 的给定 FactoryBean，在容器上调用 getBean("myBean") 将返回 FactoryBean 的乘积，而调用 getBean("\&myBean") 将返回 FactoryBean 实例本身。

#### 1.9. 基于注解的容器配置

在配置 Spring 时，注解是否比 XML 更好？

基于注解的配置的引入提出了这种方法是否比 XML“更好”的问题。简短的回答是“视情况而定”。长答案是每种方法都有其优点和缺点，通常由开发人员决定哪种策略更适合他们。由于它们的定义方式，注解在其声明中提供了大量上下文，从而使配置更短、更简洁。然而，XML 擅长在不触及源代码或重新编译它们的情况下连接组件。一些开发人员更喜欢在源附近进行布线，而另一些开发人员则认为带注解的类不再是 POJO，此外，配置变得分散且更难控制。

无论选择如何，Spring 都可以同时适应这两种风格，甚至可以将它们混合在一起。值得指出的是，通过其[JavaConfig](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java)选项，Spring 允许以非侵入性的方式使用注解，而无需触及目标组件的源代码，并且在工具方面， [Spring Tools for Eclipse](https://spring.io/tools)支持所有配置样式。

基于注解的配置提供了 XML 设置的替代方案，它依赖于字节码元数据来连接组件，而不是尖括号声明。开发人员不使用 XML 来描述 bean 连接，而是通过在相关类、方法或字段声明上使用注解将配置移动到组件类本身。如[示例中所述：`AutowiredAnnotationBeanPostProcessor`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-bpp-examples-aabpp) ,`BeanPostProcessor`与注解一起使用是扩展 Spring IoC 容器的常用方法。例如，Spring 2.0 引入了使用[`@Required`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-required-annotation)注解强制执行所需属性的可能性。Spring 2.5 使得遵循相同的通用方法来驱动 Spring 的依赖注入成为可能。本质上，`@Autowired`[annotation 提供了与Autowiring Collaborators](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire)中描述的相同的功能，但具有更细粒度的控制和更广泛的适用性。Spring 2.5 还增加了对 JSR-250 注解的支持，例如 `@PostConstruct`和`@PreDestroy`. Spring 3.0 增加了对包中包含的 JSR-330（Java 依赖注入）注解的支持，`javax.inject`例如`@Inject` 和`@Named`. 有关这些注解的详细信息，请参见 [相关部分](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-standard-annotations)。

注解注入在 XML 注入之前执行。因此，XML 配置覆盖了通过这两种方法连接的属性的注解。

与往常一样，您可以将后处理器注册为单独的 bean 定义，但也可以通过在基于 XML 的 Spring 配置中包含以下标记来隐式注册它们（注意包含`context`命名空间）：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>
```

该`<context:annotation-config/>`元素隐式注册以下后处理器：

* [`ConfigurationClassPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/ConfigurationClassPostProcessor.html)
* [`AutowiredAnnotationBeanPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html)
* [`CommonAnnotationBeanPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.html)
* [`PersistenceAnnotationBeanPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.html)
* [`EventListenerMethodProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/event/EventListenerMethodProcessor.html)

`<context:annotation-config/>`仅在定义它的同一应用程序上下文中查找 bean 上的注解。这意味着，如果您在为 `DispatcherServlet`所属的`WebApplicationContext`中配置 `<context:annotation-config/>` ，它只会检查您的controllers中的`@Autowired` bean，而不是您的服务。有关详细信息，请参阅 [DispatcherServlet](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet)。

**1.9.1. @Required**

`@Required`注解适用于 bean 属性设置方法，如下例所示：

```java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

此注解指示必须在配置时通过 bean 定义中的显式属性值或通过自动装配来填充受影响的 bean 属性。如果受影响的 bean 属性尚未填充，则容器将引发异常。这允许急切和明确`NullPointerException` 的失败，避免以后出现实例等。我们仍然建议您将断言放入 bean 类本身（例如放入 init 方法）。即使您在容器外部使用类，这样做也会强制执行这些必需的引用和值。

必须将其[`RequiredAnnotationBeanPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/annotation/RequiredAnnotationBeanPostProcessor.html) 注册为 bean 以启用对`@Required`注解的支持。

从Spring Framework 5.1 开始正式弃用`@Required`注解 和`RequiredAnnotationBeanPostProcessor`，支持使用构造函数注入进行所需设置（或自定义实现`InitializingBean.afterPropertiesSet()` 或自定义`@PostConstruct`方法以及 bean 属性设置方法）。

**1.9.2. 使用`@Autowired`**

在本节包含的示例中，`@Inject`可以使用JSR 330 的注解代替 Spring 的注解。`@Autowired`有关更多详细信息，请参见[此处](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-standard-annotations)。

您可以将`@Autowired`注解应用于构造函数，如以下示例所示：

```java
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

从 Spring Framework 4.3 开始，如果目标 bean 仅定义一个构造函数开始，则不再需要对此类构造函数进行`@Autowired`注解。但是，如果有多个构造函数可用并且没有主/默认构造函数，则必须至少对其中一个构造函数进行`@Autowired`注解，以指示容器使用哪一个构造函数。[有关详细信息，请参阅构造函数解析](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation-constructor-resolution)的讨论 。

您还可以将`@Autowired`注解应用于*传统*的setter 方法，如以下示例所示：

```java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

您还可以将注解应用于具有任意名称和多个参数的方法，如以下示例所示：

```java
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

您也可以应用于`@Autowired`字段，甚至可以将其与构造函数混合使用，如以下示例所示：

```java
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

确保您的目标组件（例如，`MovieCatalog`或`CustomerPreferenceDao`）始终由您用于带`@Autowired`注解的注入点的类型声明。否则，注入可能会由于运行时出现“找不到类型匹配”错误而失败。对于通过类路径扫描找到的 XML 定义的 bean 或组件类，容器通常预先知道具体类型。但是，对于`@Bean`工厂方法，您需要确保声明的返回类型具有足够的表现力。对于实现多个接口的组件或可能由其实现类型引用的组件，请考虑在您的工厂方法中声明最具体的返回类型（至少与引用您的 bean 的注入点所要求的一样具体）。

您还可以通过将`@Autowired`注解添加到需要该类型数组的字段或方法来指示 Spring`ApplicationContext` 提供特定类型的所有 bean ，如以下示例所示：

```java
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
```

这同样适用于类型化集合，如以下示例所示：

```java
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
```

如果您希望数组或列表中的项目按特定顺序排序，您的目标 bean 可以实现`org.springframework.core.Ordered`接口或使用`@Order`或标准`@Priority`注解。否则，它们的顺序遵循容器中相应目标 bean 定义的注册顺序。您可以`@Order`在目标类级别和`@Bean`方法上声明注解，可能针对单个 bean 定义（在使用相同 bean 类的多个定义的情况下）。`@Order`值可能会影响注入点的优先级，但请注意它们不会影响单例启动顺序，这是由依赖关系和`@DependsOn`声明确定的正交问题。请注意，标准`javax.annotation.Priority`注解在该 `@Bean`级别不可用，因为它不能在方法上声明。它的语义可以通过`@Order`结合`@Primary`每个类型的单个 bean 的值来建模。

只要预期的键类型是`String` ，即使是类型化的`Map`实例也可以自动装配。映射值包含预期类型的所有 bean，键包含相应的 bean 名称，如以下示例所示：

```java
public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
```

默认情况下，当给定注入点没有匹配的候选 bean 时，自动装配会失败。在声明的数组、集合或映射的情况下，至少需要一个匹配元素。

默认行为是将带注解的方法和字段视为指示所需的依赖项。您可以按照以下示例所示更改此行为，使框架能够通过将其标记为非必需（即，通过将`required`属性设置`@Autowired`为`false`）来跳过不可满足的注入点：

```java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

如果非必需方法的依赖项（或其依赖项之一，如果有多个参数）不可用，则根本不会调用非必需方法。在这种情况下，根本不会填充非必填字段，而保留其默认值。

注入的构造函数和工厂方法参数是一种特殊情况，因为Spring 的构造函数解析算法可能会处理多个构造函数，因此`@Autowired`中的`required` 属性的含义有些不同。默认情况下，构造函数和工厂方法参数是有效的，但在单构造函数场景中有一些特殊规则，例如如果没有匹配的 bean 可用，多元素注入点（数组、集合、映射）解析为空实例。这允许一种通用的实现模式，其中所有依赖项都可以在唯一的多参数构造函数中声明——例如，声明为没有`@Autowired`注解的单个公共构造函数。

任何给定 bean 类中只有一个构造函数可以声明 @Autowired，并将 required 属性设置为 true，指示该构造函数在用作 Spring bean 时自动装配。因此，如果 required 属性保留其默认值 true，则只能使用 @Autowired 注解单个构造函数。如果多个构造函数声明该注释，则它们都必须声明 required=false 才能被视为自动装配的候选者（类似于 XML 中的 autowire=constructor）。将选择具有最大数量的依赖关系的构造函数，这些依赖关系可以通过匹配 Spring 容器中的 bean 来满足。如果没有一个候选可以满足，则将使用主要/默认构造函数（如果存在）。类似地，如果一个类声明了多个构造函数，但没有一个构造函数用 @Autowired 注释，则将使用主/默认构造函数（如果存在）。如果一个类一开始只声明一个构造函数，那么即使没有注释，它也将始终被使用。请注意，带注释的构造函数不必是公共的。

或者，您可以通过 Java 8 表达特定依赖项的非必需性质`java.util.Optional`，如以下示例所示：

```java
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
```

从 Spring Framework 5.0 开始，您还可以使用`@Nullable`注解（任何包中的任何类型 - 例如，`javax.annotation.Nullable`来自 JSR-305）或仅利用 Kotlin 内置的空安全支持：

```java
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}
```

您还可以`@Autowired`用于众所周知的可解析依赖项的接口：`BeanFactory`、`ApplicationContext`、`Environment`、`ResourceLoader`、 `ApplicationEventPublisher`和`MessageSource`. 这些接口及其扩展接口，例如`ConfigurableApplicationContext`或 `ResourcePatternResolver`，会自动解析，无需特殊设置。以下示例自动装配一个`ApplicationContext`对象：

```java
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
```

`@Resource`、`@Autowired`、`@Inject`和`@Value`注解由 Spring`BeanPostProcessor` 实现处理。这意味着您不能在您自己的`BeanPostProcessor`或类型`BeanFactoryPostProcessor`（如果有）中应用这些注解。这些类型必须通过使用 XML 或 Spring `@Bean` 方法显式“连接”起来。

**1.9.3. 微调基于注解的自动装配`@Primary`**

由于按类型自动装配可能会导致多个候选者，因此通常需要对选择过程进行更多控制。实现这一点的一种方法是使用 Spring 的 `@Primary`注解。`@Primary`指示当多个 bean 是自动装配到单值依赖项的候选对象时，应该优先考虑特定的 bean。如果候选中恰好存在一个主 bean，则它将成为自动装配的值。

考虑以下定义`firstMovieCatalog`为主要的配置`MovieCatalog`：

```java
@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
```

使用上述配置，以下`MovieRecommender`内容与 自动装配 `firstMovieCatalog`：

```java
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
```

对应的bean定义如下：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
```

**1.9.4. 使用限定符微调基于注解的自动装配**

当可以确定一个主要候选者时，是一种通过类型`@Primary`使用多个实例的自动装配的有效方法。当您需要对选择过程进行更多控制时，可以使用 Spring 的`@Qualifier`注解。您可以将限定符值与特定参数相关联，缩小类型匹配的作用域，以便为每个参数选择特定的 bean。在最简单的情况下，这可以是一个简单的描述性值，如以下示例所示：

```java
public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}
```

您还可以在单个构造函数参数或方法参数上指定`@Qualifier`注解，如下例所示：

```java
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
```

以下示例显示了相应的 bean 定义。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
```

对于回调匹配，bean 名称被视为默认限定符值。因此，您可以使用`main`的`id`代替嵌套的限定符元素来定义 bean ，从而获得相同的匹配结果。但是，尽管您可以使用此约定按名称引用特定 bean，但从`@Autowired`根本上讲，它是关于带有可选语义限定符的类型驱动注入。这意味着限定符值，即使使用 bean 名称回退，也总是在类型匹配集中具有缩小的语义。它们不会在语义上表达对唯一 bean `id`的引用。好的限定符值是`main` 或 `EMEA`或 `persistent`，表示独立于 bean `id`的特定组件的特征，在匿名 bean 定义（例如前面示例中的那个）的情况下，它可能会自动生成。

如前所述，限定符也适用于类型化集合——例如，对`Set<MovieCatalog>`. 在这种情况下，根据声明的限定符，所有匹配的 bean 都作为集合注入。这意味着限定符不必是唯一的。相反，它们构成过滤标准。例如，您可以定义多个`MovieCatalog`具有相同限定符值“action”的 bean，所有这些 bean 都被注入到带有`@Qualifier("action")`的`Set<MovieCatalog>`.

在类型匹配的候选对象中，让限定符值针对目标 bean 名称进行选择，不需要在注入点进行`@Qualifier`注解。如果没有其他解析指标（例如限定符或主标记），对于非唯一依赖情况，Spring 将注入点名称（即字段名称或参数名称）与目标 bean 名称匹配并选择同名候选人（如有）。

也就是说，如果您打算按名称表示注解驱动的注入，请不要主要使用`@Autowired`，即使它能够在类型匹配候选者中按 bean 名称进行选择。相反，使用 JSR-250`@Resource`注解，它在语义上定义为通过其唯一名称标识特定目标组件，声明的类型与匹配过程无关。`@Autowired`具有相当不同的语义：在按类型选择候选 bean 之后，指定的`String` 限定符值仅在那些类型选择的候选者中考虑（例如，将`account`限定符与标记有相同限定符标签的 bean 匹配）。

对于本身定义为集合`Map`或数组类型的 bean，这`@Resource` 是一个很好的解决方案，通过唯一名称引用特定的集合或数组 bean。也就是说，从 4.3 开始，您也可以通过 Spring 的`@Autowired`类型匹配算法匹配集合、`Map`和数组类型 ，只要元素类型信息保留在`@Bean`返回类型签名或集合继承层次结构中即可。在这种情况下，您可以使用限定符值在相同类型的集合中进行选择，如上一段所述。

从 4.3 开始，`@Autowired`还考虑了注入的自引用（即，对当前注入的 bean 的引用）。请注意，自注入是一种后备。对其他组件的常规依赖始终具有优先权。从这个意义上说，自我参考不参与常规的候选人选择，因此尤其不是主要的。相反，它们总是以最低优先级结束。在实践中，您应该仅将自引用用作最后的手段（例如，通过 bean 的事务代理在同一实例上调用其他方法）。在这种情况下，考虑将受影响的方法分解为单独的委托 bean。或者，您可以使用`@Resource`，它可以通过其唯一名称获取返回到当前 bean 的代理。

尝试从`@Bean`同一配置类上的方法注入结果实际上也是一种自引用场景。要么在实际需要的方法签名中延迟解析此类引用（与配置类中的自动装配字段相反），要么将受影响的`@Bean`方法声明为`static`，将它们与包含的配置类实例及其生命周期解耦。否则，仅在回退阶段考虑此类 bean，而将其他配置类上的匹配 bean 选为主要候选者（如果可用）。

`@Autowired`适用于字段、构造函数和多参数方法，允许在参数级别通过限定符注解缩小作用域。相反，`@Resource` 仅支持具有单个参数的字段和 bean 属性设置器方法。因此，如果您的注入目标是构造函数或多参数方法，您应该坚持使用限定符。

您可以创建自己的自定义限定符注解。为此，请定义注解并`@Qualifier`在定义中提供注解，如以下示例所示：

```java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}
```

然后，您可以在自动装配的字段和参数上提供自定义限定符，如以下示例所示：

```java
public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}
```

接下来，您可以提供候选 bean 定义的信息。您可以添加 `<qualifier/>`标签作为`<bean/>`标签的子元素，然后指定`type`和`value`以匹配您的自定义限定符注解。该类型与注解的完全限定类名匹配。或者，如果不存在名称冲突的风险，为方便起见，您可以使用短类名。以下示例演示了这两种方法：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
```

在[Classpath Scanning and Managed Components](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-classpath-scanning)中，您可以看到基于注解的替代方法，以在 XML 中提供限定符元数据。具体来说，请参阅[提供带有注解的限定符元数据](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-qualifiers)。

在某些情况下，使用没有值的注解可能就足够了。当注解服务于更通用的目的并且可以应用于多种不同类型的依赖项时，这可能很有用。例如，您可以提供一个离线目录，当没有可用的 Internet 连接时可以搜索该目录。首先，定义简单的注解，如下例所示：

```java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}
```

然后将注解添加到要自动装配的字段或属性中，如下例所示：

```java
public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}
```

现在 bean 定义只需要一个 qualifier `type`，如下例所示：

```xml
<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> 
    <!-- inject any dependencies required by this bean -->
</bean>
```

除了`value`或代替简单属性，您还可以定义接受命名属性的自定义限定符注解。如果随后在要自动装配的字段或参数上指定多个属性值，则 bean 定义必须匹配所有此类属性值才能被视为自动装配候选者。例如，考虑以下注解定义：

```java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}
```

在这种情况下`Format`是一个枚举，定义如下：

```java
public enum Format {
    VHS, DVD, BLURAY
}
```

要自动装配的字段使用自定义限定符进行注解，并包括两个属性的值：`genre`和`format`，如以下示例所示：

```java
public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}
```

最后，bean 定义应该包含匹配的限定符值。此示例还演示了您可以使用 bean 元属性而不是 `<qualifier/>`元素。如果可用，则`<qualifier/>`元素及其属性优先，但 如果不存在此类限定符，则自动装配机制将依赖`<meta/>`标签中提供的值，如以下示例中的最后两个 bean 定义：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>
```

**1.9.5. 使用泛型作为自动装配限定符**

除了`@Qualifier`注解之外，您还可以使用 Java 泛型类型作为限定的隐式形式。例如，假设您有以下配置：

```java
@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}
```

假设前面的 bean 实现了一个泛型接口，（即`Store<String>`和 `Store<Integer>`），您可以`@Autowire`将`Store`接口和泛型用作限定符，如以下示例所示：

```java
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
```

通用限定符也适用于自动装配列表、`Map`实例和数组。以下示例自动装配一个泛型`List`：

```java
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
```

**1.9.6. 使用`CustomAutowireConfigurer`**

[`CustomAutowireConfigurer`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/annotation/CustomAutowireConfigurer.html) 是一个`BeanFactoryPostProcessor`允许您注册自己的自定义限定符注解类型，即使它们没有使用 Spring 的注解进行`@Qualifier`注解。下面的例子展示了如何使用`CustomAutowireConfigurer`：

```xml
<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>
```

通过以下`AutowireCandidateResolver`方式确定自动接线候选者：

* `autowire-candidate`每个bean定义的值
* 元素`default-autowire-candidates`上可用的任何模式`<beans/>`
* 注解的存在`@Qualifier`和任何注册的自定义注解`CustomAutowireConfigurer`

当多个 bean 有资格成为自动装配候选者时，“主要”的确定如下：如果候选者中恰好一个 bean 定义的`primary` 属性设置为`true`，则选择它。

**1.9.7. 注射用`@Resource`**

Spring 还通过在字段或 bean 属性设置器方法上使用 JSR-250`@Resource`注解 ( )来支持注入。`javax.annotation.Resource`这是 Java EE 中的常见模式：例如，在 JSF 管理的 bean 和 JAX-WS 端点中。Spring 也支持 Spring 管理的对象的这种模式。

`@Resource`采用名称属性。默认情况下，Spring 将该值解释为要注入的 bean 名称。换句话说，它遵循按名称语义，如以下示例所示：

```java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
```

如果没有明确指定名称，则默认名称派生自字段名称或 setter 方法。如果是字段，则采用字段名称。对于 setter 方法，它采用 bean 属性名称。以下示例将把名为 bean 的 bean`movieFinder`注入到它的 setter 方法中：

```java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
```

随注解提供的名称被解析为 bean 名称，由 bean `ApplicationContext`知道`CommonAnnotationBeanPostProcessor`。如果显式配置 Spring，则可以通过 JNDI 解析名称 [`SimpleJndiBeanFactory`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/jndi/support/SimpleJndiBeanFactory.html) 。但是，我们建议您依赖默认行为并使用 Spring 的 JNDI 查找功能来保留间接级别。

在没有指定显式名称的排他性`@Resource`使用情况下，类似于`@Autowired`，`@Resource`查找主类型匹配而不是特定的命名 bean 并解析众所周知的可解析依赖项：`BeanFactory`、 `ApplicationContext`、`ResourceLoader`、`ApplicationEventPublisher`和`MessageSource` 接口。

因此，在以下示例中，该`customerPreferenceDao`字段首先查找名为“customerPreferenceDao”的 bean，然后回退到 type 的主要类型匹配 `CustomerPreferenceDao`：

```java
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}
```

该`context`字段是根据已知的可解析依赖类型注入的： `ApplicationContext`.

**1.9.8. 使用`@Value`**

`@Value`通常用于注入外部属性：

```java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
```

使用以下配置：

```java
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
```

以及以下`application.properties`文件：

```java
catalog.name=MovieCatalog
```

在这种情况下，`catalog`参数和字段将等于该`MovieCatalog`值。

Spring 提供了一个默认的宽松嵌入式值解析器。它将尝试解析属性值，如果无法解析，属性名称（例如`${catalog.name}`）将作为值注入。如果要严格控制不存在的值，则应声明一个`PropertySourcesPlaceholderConfigurer`bean，如以下示例所示：

```java
@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}
```

配置`PropertySourcesPlaceholderConfigurer`使用 JavaConfig 时， `@Bean`方法必须是`static`.

`${}` 如果无法解析任何占位符，使用上述配置可确保 Spring 初始化失败。也可以使用 `setPlaceholderPrefix`, `setPlaceholderSuffix`, 或`setValueSeparator`自定义占位符等方法。

Spring Boot 默认配置一个`PropertySourcesPlaceholderConfigurer`bean，该 bean 将从`application.properties`和`application.yml`文件中获取属性。

Spring 提供的内置转换器支持允许自动处理简单的类型转换（to`Integer` 或example）。`int`多个逗号分隔的值可以自动转换为`String`数组，无需额外的努力。

可以提供如下默认值：

```java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}
```

Spring在后台`BeanPostProcessor`使用 a来处理将值转换为目标类型的过程。如果您想为您自己的自定义类型提供转换支持，您可以提供您自己的 bean 实例，如以下示例所示：`ConversionService``String``@Value``ConversionService`

```java
@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}
```

当`@Value`包含[`SpEL`表达式](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions)时，该值将在运行时动态计算，如以下示例所示：

```java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}
```

SpEL 还支持使用更复杂的数据结构：

```java
@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}
```

**1.9.9. 使用`@PostConstruct`和`@PreDestroy`**

`CommonAnnotationBeanPostProcessor`不仅可以识别`@Resource`注解，还可以识别 JSR-250 生命周期注解：`javax.annotation.PostConstruct`和 `javax.annotation.PreDestroy`. [在 Spring 2.5 中引入，对这些注解的支持提供了初始化回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean)和 [销毁回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-disposablebean)中描述的生命周期回调机制的替代方案 。如果 在 Spring `ApplicationContext`中注册了`CommonAnnotationBeanPostProcessor`，则在生命周期中与对应的 Spring 生命周期接口方法或显式声明的回调方法相同的点调用带有这些注解之一的方法。在以下示例中，缓存在初始化时预先填充并在销毁时清除：

```java
public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}
```

组合各种生命周期机制的效果的详细信息，请参见 [组合生命周期机制](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-combined-effects)。

就像`@Resource`，`@PostConstruct`和`@PreDestroy`注解类型是从 JDK 6 到 8 的标准 Java 库的一部分。但是，整个`javax.annotation` 包在 JDK 9 中与核心 Java 模块分离，并最终在 JDK 11 中被删除。如果需要，`javax.annotation-api`工件需要现在通过 Maven Central 获得，只需像任何其他库一样添加到应用程序的类路径中。

#### 1.10. 类路径扫描和管理组件

本章中的大多数示例都使用 XML 来指定在 Spring 容器内生成每个 `BeanDefinition` 的配置元数据。上一节（[基于注解的容器配置](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-annotation-config)) 演示如何通过source级注解提供大量配置元数据。然而，即使在这些示例中，“基本”bean 定义也在 XML 文件中明确定义，而注解仅驱动依赖注入。本节描述了通过扫描类路径隐式检测候选组件的选项。候选组件是与过滤条件匹配的类，并在容器中注册了相应的 bean 定义。这消除了使用 XML 来执行 bean 注册的需要。相反，您可以使用注解（例如`@Component`）、AspectJ 类型表达式或您自己的自定义过滤条件来选择哪些类具有向容器注册的 bean 定义。

从 Spring 3.0 开始，Spring JavaConfig 项目提供的许多特性都是核心 Spring Framework 的一部分。这允许您使用 Java 而不是使用传统的 XML 文件来定义 bean。查看`@Configuration`、`@Bean`、 `@Import`和`@DependsOn`注解，了解如何使用这些新功能的示例。

**1.10.1. `@Component`和进一步的原型注解**

注解`@Repository`是满足存储库（也称为数据访问对象或 DAO）角色或原型的任何类的标记。此标记的用途之一是异常的自动翻译，如 [Exception Translation](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#orm-exception-translation)中所述。

Spring 提供了更多的原型注解：`@Component`、`@Service`和 `@Controller`. `@Component`是任何 Spring 管理的组件的通用构造型。 `@Repository`, `@Service`和`@Controller`是`@Component`针对更具体用例（分别在持久层、服务层和表示层）的特化。因此，您可以使用 `@Component` 注解组件类，但是通过使用 `@Repository`、`@Service` 或者`@Controller`注解它们，您的类更适合工具处理或与切面关联。例如，这些原型注解是切入点的理想目标。`@Repository`, `@Service`, 和`@Controller`还可以在 Spring 框架的未来版本中携带额外的语义。因此，如果您在对于你的服务层使用`@Component`或者`@Service`，`@Service`显然是更好的选择。同样，如前所述，`@Repository`已经支持作为持久层中自动异常转换的标记。

**1.10.2. 使用元注解和组合注解**

Spring 提供的许多注解都可以在您自己的代码中用作元注解。元注解是可以应用于另一个注解的注解。例如，前面提到的[注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-stereotype-annotations)`@Service`是 用`@Component` 元注解的，如以下示例所示：

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ...
}
```

您还可以组合元注解来创建“组合注解”。例如，Spring MVC 的`@RestController`注解由`@Controller`和`@ResponseBody` 组成。

此外，组合注解可以选择从元注解中重新声明属性以允许自定义。当您只想公开元注解属性的子集时，这可能特别有用。例如，Spring 的 `@SessionScope`注解将作用域名称硬编码为`session`，但仍允许自定义`proxyMode`. 以下清单显示了 `SessionScope`注解的定义：

```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}
```

然后，您可以在`@SessionScope`不声明`proxyMode`如下的情况下使用：

```java
@Service
@SessionScope
public class SessionScopedService {
    // ...
}
```

您还可以覆盖`proxyMode` 的值，如以下示例所示：

```java
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}
```

有关更多详细信息，请参阅 [Spring Annotation Programming Model](https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model) wiki 页面。

**1.10.3. 自动检测类和注册 Bean 定义**

Spring可以自动检测构造型类并向`ApplicationContext`注册相应的`BeanDefinition`实例。 例如，以下两个类可以进行此类自动检测：

```java
@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
```

```java
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}
```

要自动检测这些类并注册相应的 bean，您需要添加 `@ComponentScan`到您的`@Configuration`类中，其中`basePackages`属性是两个类的公共父包。（或者，您可以指定一个逗号或分号或空格分隔的列表，其中包括每个类的父包。）

```java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
```

为简洁起见，前面的示例可能使用了注解的`value`属性（即`@ComponentScan("org.example")`）。

以下替代方法使用 XML：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>
```

使用`<context:component-scan>`会隐式启用 `<context:annotation-config>`. 使用`<context:component-scan>`时通常不需要包含该 `<context:annotation-config>`元素。

类路径包的扫描需要类路径中存在相应的目录条目。当您使用 Ant 构建 JAR 时，请确保您没有激活 JAR 任务的仅文件开关。此外，在某些环境中，类路径目录可能不会根据安全策略公开——例如，JDK 1.7.0\_45 及更高版本上的独立应用程序（需要在清单中设置“Trusted-Library”——请参阅 [https://stackoverflow.com/问题/19394570/java-jre-7u45-breaks-classloader-getresources](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)）。

在 JDK 9 的模块路径（Jigsaw）上，Spring 的类路径扫描通常按预期工作。但是，请确保您的组件类在您的`module-info` 描述符中导出。如果您希望 Spring 调用类的非公共成员，请确保它们是“开放的”（即，它们使用`opens`声明而不是`module-info`描述符中的 `exports`声明）。

此外，当您使用 component-scan 元素时， `AutowiredAnnotationBeanPostProcessor`和`CommonAnnotationBeanPostProcessor`都被隐式包含在内。这意味着这两个组件会被自动检测并连接在一起——所有这些都不需要 XML 中提供任何 bean 配置元数据。

您可以禁用注册`AutowiredAnnotationBeanPostProcessor`和`CommonAnnotationBeanPostProcessor`通过设置属性`annotation-config`值为`false` 。

**1.10.4. 使用过滤器自定义扫描**

默认情况下，使用`@Component`、`@Repository`、`@Service`、`@Controller`、 `@Configuration`注解的类或本身带有`@Component`注解的自定义注解是唯一检测到的候选组件。但是，您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为 `@ComponentScan` 注释的 `includeFilters` 或 `exceptFilters` 属性（或者作为 XML 配置中`<context:component-scan>`元素的`<context:include-filter />` 或 `<context:exclude-filter />` 子元素）。每个过滤器元素都需要类型和表达式属性。下表描述了过滤选项：

| 过滤器类型   | 示例表达式                        | 描述                                                  |
| ------- | ---------------------------- | --------------------------------------------------- |
| 注解（默认）  | `org.example.SomeAnnotation` | 在目标组件的类型级别*存在*或*元存在*的注解。                            |
| 可分配的    | `org.example.SomeClass`      | 目标组件可分配（扩展或实现）的类（或接口）。                              |
| aspectj | `org.example..*Service+`     | 要由目标组件匹配的 AspectJ 类型表达式。                            |
| 正则表达式   | `org\.example\.Default.*`    | 与目标组件的类名匹配的正则表达式。                                   |
| 自定义     | `org.example.MyTypeFilter`   | 接口的自定义实现`org.springframework.core.type.TypeFilter`。 |

以下示例显示了忽略所有`@Repository`注解并使用“存根”存储库的配置：

```java
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    // ...
}
```

以下清单显示了等效的 XML：

```xml
<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
```

您还可以通过在注解上设置`useDefaultFilters=false`或`use-default-filters="false"`作为 `<component-scan/>`元素的属性提供来禁用默认过滤器。这有效地禁用了用`@Component`, `@Repository`, `@Service`, `@Controller`, `@RestController`, 或`@Configuration`注解或元注解的类的自动检测。

**1.10.5. 在组件中定义 Bean 元数据**

Spring 组件还可以将 bean 定义元数据贡献给容器。您可以使用`@Bean`用于在带 `@Configuration`注解的类中定义 bean 元数据的相同注解来执行此操作。以下示例显示了如何执行此操作：

```java
@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}
```

> 前面的类是一个 Spring 组件，它的 `doWork()`方法中包含特定于应用程序的代码。但是，它还提供了一个 bean 定义，该定义具有引用方法的工厂方法`publicInstance()`。`@Bean`注解标识工厂方法和其他 bean 定义属性，例如通过注解`@Qualifier`的限定符值。可以指定的其他方法级注解是 `@Scope`,`@Lazy`和自定义限定符注解。
>
> 除了用于组件初始化之外，您还可以将 `@Lazy` 注解放置在标有 `@Autowired` 或 `@Inject` 的注入点上。在这种情况下，它会导致注入惰性解析代理。然而，这种代理方法相当有限。对于复杂的惰性交互，特别是与可选依赖项结合使用，我们建议改为使用 `ObjectProvider<MyTargetBean>`。

如前所述，支持自动装配的字段和方法，并额外支持`@Bean`方法的自动装配。以下示例显示了如何执行此操作：

```java
@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}
```

该示例将方法`String`类型的参数 `country`自动连接到 另一个名为`privateInstance`的 bean 上的`age`属性值。Spring 表达式语言元素通过`#{ <expression> }`表示法定义属性的值。对于`@Value` 注解，表达式解析器被预先配置为在解析表达式文本时查找 bean 名称。

从 Spring Framework 4.3 开始，您还可以声明类型 `InjectionPoint`（或其更具体的子类：`DependencyDescriptor`）的工厂方法参数来访问触发当前 bean 创建的请求注入点。请注意，这仅适用于 bean 实例的实际创建，不适用于现有实例的注入。因此，此功能对于原型范围的 bean 最有意义。对于其他范围，工厂方法只看到在给定范围内触发创建新 bean 实例的注入点（例如，触发创建惰性单例 bean 的依赖项）。在这种情况下，您可以使用提供的带有语义关怀的注入点元数据。下面的例子展示了如何使用`InjectionPoint`：

```java
@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}
```

常规 Spring 组件中的方法的处理方式与 Spring `@Configuration`类中`@Bean`的对应方法不同。不同之处在于`@Component` 类没有通过 CGLIB 增强来拦截方法和字段的调用。CGLIB 代理是调用`@Configuration`类`@Bean`方法中的方法或字段创建协作对象的 bean 元数据引用的方法。这样的方法不是用普通的 Java 语义调用的，而是通过容器来提供 Spring bean 的通常的生命周期管理和代理，即使通过对`@Bean`方法的编程调用来引用其他 bean 也是如此。相比之下，在普通 `@Component` 类中调用`@Bean`方法中的方法或字段具有标准 Java 语义，无需特殊的 CGLIB 处理或其他约束。

您可以将`@Bean`方法声明为`static`，允许在不创建包含它们的配置类作为实例的情况下调用它们。 这在定义后处理器 bean（例如，类型`BeanFactoryPostProcessor`或 `BeanPostProcessor`）时特别有意义，因为这些 bean 在容器生命周期的早期就被初始化，并且应该避免在那个时候触发配置的其他部分。

由于技术限制，对静态`@Bean`方法的调用永远不会被容器拦截，甚至在 `@Configuration`类中也不会（如本节前面所述）：CGLIB 子类化只能覆盖非静态方法。因此，直接调用另一个具有标准的 Java 语义的`@Bean`方法，从而导致直接从工厂方法本身返回一个独立的实例。

`@Bean`方法的 Java 语言可见性不会立即影响 Spring 容器中生成的 bean 定义。您可以自由地声明您认为适合非`@Configuration`类的工厂方法，也可以在任何地方声明静态方法。但是，类中的常规`@Bean`方法`@Configuration`需要是可覆盖的——也就是说，它们不能被声明为`private`或 `final`。

`@Bean`方法也在给定组件或配置类的基类上发现，以及在组件或配置类实现的接口中声明的 Java 8 默认方法上发现。这为组合复杂的配置安排提供了很大的灵活性，甚至可以通过 Spring 4.2 的 Java 8 默认方法实现多重继承。

最后，单个类可以为同一个 bean 创建`@Bean`多个方法，作为多个工厂方法的排列，根据运行时可用的依赖关系使用。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法的算法相同：在构造时选择具有最多可满足依赖项的变体，类似于容器如何在多个`@Autowired`构造函数之间进行选择。

**1.10.6. 命名自动检测到的组件**

当一个组件作为扫描过程的一部分被自动检测到时，它的 Bean 名称由该扫描器已知的 `BeanNameGenerator` 策略生成。默认情况下，任何包含名称值的 Spring 构造型注释（`@Component`、`@Repository`、`@Service` 和 `@Controller`）都会将该名称提供给相应的 bean 定义。

如果这样的注解不包含名称`value`或任何其他检测到的组件（例如由自定义过滤器发现的组件），则默认 bean 名称生成器将返回未大写的非限定类名称。例如，如果检测到以下组件类，则名称为`myMovieLister`和 `movieFinderImpl`：

```java
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
```

```java
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
```

如果您不想依赖默认的 bean 命名策略，可以提供自定义 bean 命名策略。首先，实现 [`BeanNameGenerator`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/support/BeanNameGenerator.html) 接口，并确保包含一个默认的无参数构造函数。然后，在配置扫描器时提供完全限定的类名，如以下示例注解和 bean 定义所示。

如果由于多个自动检测到的组件具有相同的非限定类名（即，具有相同名称但位于不同包中的类）而遇到命名冲突，您可能需要配置`BeanNameGenerator`默认为生成的完全限定类名的Bean名。从 Spring Framework 5.2.3 开始， 位于`org.springframework.context.annotation`包中的`FullyQualifiedAnnotationBeanNameGenerator`可用于此类目的。

```java
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>
```

作为一般规则，只要其他组件可能显式引用它，请考虑使用注解指定名称。另一方面，只要容器负责接线，自动生成的名称就足够了。

**1.10.7. 为自动检测的组件提供作用域**

与一般 Spring 管理的组件一样，自动检测组件的默认和最常见作用域是`singleton`. 但是，有时您需要可以由`@Scope`注解指定的不同作用域。您可以在注解中提供作用域的名称，如以下示例所示：

```java
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
```

`@Scope`注解仅在具体 bean 类（用于注解组件）或工厂方法（用于`@Bean`方法）上进行自省。与 XML bean 定义相比，没有 bean 定义继承的概念，并且类级别的继承层次结构与元数据无关。

有关 Web 特定范围的详细信息，例如 Spring 上下文中的“请求”或“会话”，请参阅[请求、会话、应用程序和 WebSocket 范围](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other)。与这些范围的预构建注解一样，您也可以使用 Spring 的元注解方法来编写自己的范围注解：例如，使用元注解`@Scope("prototype")`的自定义注解，也可能声明自定义范围代理模式。

要为范围解析提供自定义策略而不是依赖基于注解的方法，您可以实现该 [`ScopeMetadataResolver`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/ScopeMetadataResolver.html) 接口。确保包含一个默认的无参数构造函数。然后，您可以在配置扫描器时提供完全限定的类名，如以下注解和 bean 定义示例所示：

```java
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
```

当使用某些非单例作用域时，可能需要为作用域对象生成代理。原因在[Scoped Beans as Dependencies](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other-injection)中进行了描述。为此，component-scan 元素上提供了 scoped-proxy 属性。三个可能的值是：`no`、`interfaces`和`targetClass`。例如，以下配置会生成标准 JDK 动态代理：

```java
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
```

**1.10.8. 提供带有注解的限定符元数据**

`@Qualifier`注解在 [Fine-tuning Annotation-based Autowiring with Qualifiers](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation-qualifiers)中讨论。该部分中的示例演示了使用`@Qualifier`注解和自定义限定符注解在解析自动装配候选时提供细粒度控制。因为这些示例基于 XML bean 定义，所以通过使用 XML 中`bean`元素的`qualifier`或`meta` 子元素在候选 bean 定义上提供限定符元数据。当依赖类路径扫描来自动检测组件时，您可以在候选类上为限定符元数据提供类型级别的注解。以下三个示例演示了这种技术：

```java
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
```

```java
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
```

```java
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
```

与大多数基于注解的替代方案一样，请记住注解元数据绑定到类定义本身，而 XML 的使用允许相同类型的多个 bean 提供其限定符元数据的变体，因为元数据是根据每个-实例而不是每个类。

**1.10.9. 生成候选组件的索引**

虽然类路径扫描非常快，但可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。在这种模式下，作为组件扫描目标的所有模块都必须使用这种机制。

您现有的`@ComponentScan`或`<context:component-scan/>`指令必须保持不变，才能请求上下文以扫描某些包中的候选人。当 `ApplicationContext`检测到这样的下标时，它会自动使用它而不是扫描类路径。

要生成索引，请向每个包含作为组件扫描指令目标的组件的模块添加一个附加依赖项。以下示例显示了如何使用 Maven 执行此操作：

```xml
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.22</version>
        <optional>true</optional>
    </dependency>
</dependencies>
```

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

```groovy
dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.3.22"
}
```

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

```groovy
dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:5.3.22"
}
```

`spring-context-indexer`artifact 会生成一个包含在 jar 文件中的`META-INF/spring.components`文件。

在 IDE 中使用此模式时，`spring-context-indexer`必须将其注册为注解处理器，以确保更新候选组件时索引是最新的。

当在类路径中找到`META-INF/spring.components`文件时，索引会自动启用。如果索引对某些库（或用例）部分可用，但无法为整个应用程序构建，您可以通过设置`spring.index.ignore`为`true`（作为JVM 系统属性或通过 [`SpringProperties`](https://docs.spring.io/spring-framework/docs/current/reference/html/appendix.html#appendix-spring-properties)机制）来回退到常规类路径安排（好像根本不存在索引）

#### 1.11. 使用 JSR 330 标准注解

从 Spring 3.0 开始，Spring 提供对 JSR-330 标准注解（依赖注入）的支持。这些注解的扫描方式与 Spring 注解相同。要使用它们，您需要在类路径中有相关的 jar。

如果您使用 Maven，则`javax.inject`工件在标准 Maven 存储库 ( <https://repo1.maven.org/maven2/javax/inject/javax.inject/1/> ) 中可用。您可以将以下依赖项添加到文件 pom.xml 中：

```xml
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version> 
</dependency>
```

**1.11.1. `@Inject`和`@Named`依赖注入**

相对于`@Autowired`，您可以使用`@javax.inject.Inject`如下：

```java
import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        // ...
    }
}
```

与 `@Autowired`一样，您可以在字段级别、方法级别和构造函数参数级别使用`@Inject`。此外，您可以将注入点声明为 `Provider`，从而允许按需访问范围更短的 bean 或通过`Provider.get()`调用延迟访问其他 bean。以下示例提供了前面示例的变体：

```java
import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        // ...
    }
}
```

如果您想为应该注入的依赖项使用限定名称，则应使用`@Named`注解，如以下示例所示：

```java
import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

与`@Autowired`相同,`@Inject`也可以与`java.util.Optional`或 `@Nullable`一起使用。`@Inject`在这里更适用，因为没有`required`属性。以下一对示例展示了如何使用`@Inject`和 `@Nullable`：

```java
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}
```

```java
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}
```

**1.11.2.`@Named`和`@ManagedBean`注解：`@Component`的标准等效项**

您可以使用`@javax.inject.Named`或`javax.annotation.ManagedBean`代替`@Component`，如以下示例所示：

```java
import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

在不指定组件名称的情况下 使用`@Component`是很常见的。可以以类似的方式使用`@Named`，如以下示例所示：

```java
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
```

当您使用`@Named`或`@ManagedBean`时，您可以使用与使用 Spring 注解时完全相同的方式使用组件扫描，如以下示例所示：

```java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
```

与 `@Component`相比，JSR-330`@Named`和 JSR-250`@ManagedBean` 注解是不可组合的。您应该使用 Spring 的原型模型来构建自定义组件注解。

**1.11.3. JSR-330 标准注解的限制**

使用标准注解时，您应该知道某些重要功能不可用，如下表所示：

| spring              | javax.inject.\*       | javax.inject 限制/注释                                                                                                                                                                                                                                                             |
| ------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| @Autowired          | @Inject               | `@Inject`没有“required”属性。可以与 Java 8 `Optional`一起使用。                                                                                                                                                                                                                             |
| @Component          | @Named / @ManagedBean | JSR-330 不提供可组合模型，仅提供一种识别命名组件的方法。                                                                                                                                                                                                                                               |
| @Scope("singleton") | @Singleton            | JSR-330 默认范围类似于 Spring 的`prototype`. 但是，为了保持它与 Spring 的一般默认值一致，在 Spring 容器中声明的 JSR-330 bean 是默认的`singleton`。为了使用`singleton` 以外的范围，您应该使用 Spring 的`@Scope`注解。`javax.inject`还提供了一个 [@Scope](https://download.oracle.com/javaee/6/api/javax/inject/Scope.html)注解。然而，这个仅用于创建您自己的注解。 |
| @Qualifier          | @Qualifier / @Named   | `javax.inject.Qualifier`只是用于构建自定义限定符的元注解。具体`String`的限定符（如`@Qualifier`带有值的 Spring）可以通过`javax.inject.Named`.                                                                                                                                                                     |
| @Value              | -                     | 没有等价物                                                                                                                                                                                                                                                                          |
| @Lazy               | -                     | 没有等价物                                                                                                                                                                                                                                                                          |
| ObjectFactory       | Provider              | `javax.inject.Provider`是 Spring `ObjectFactory`的直接替代品，只是`get()`方法名称更短。它还可以与 Spring`@Autowired`或未注解的构造函数和 setter 方法结合使用。                                                                                                                                                        |

#### 1.12. 基于 Java 的容器配置

本节介绍如何在 Java 代码中使用注解来配置 Spring 容器。它包括以下主题：

* [基本概念：`@Bean`和`@Configuration`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-basic-concepts)
* [通过使用实例化 Spring 容器`AnnotationConfigApplicationContext`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-instantiating-container)
* [使用`@Bean`注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-bean-annotation)
* [使用`@Configuration`注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-configuration-annotation)
* [组合基于 Java 的配置](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-composing-configuration-classes)
* [Bean 定义配置文件](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition-profiles)
* [`PropertySource`抽象](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-property-source-abstraction)
* [使用`@PropertySource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-using-propertysource)
* [语句中的占位符解析](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-placeholder-resolution-in-statements)

**1.12.1. 基本概念：`@Bean`和`@Configuration`**

Spring 新的 Java 配置支持中的核心工件是带 `@Configuration`注解的类和带`@Bean`注解的方法。

`@Bean`注解用于表示一个方法实例化、配置和初始化一个由 Spring IoC 容器管理的新对象。对于熟悉 Spring 的XML `<beans/>`配置的人来说，注解`@Bean`与元素`<bean/>`的作用相同。您可以将`@Bean`-annotated 方法与任何 Spring `@Component`一起 使用。但是，它们最常与`@Configuration`Bean类一起使用。

用`@Configuration` 注解一个类表明它的主要目的是作为 bean 定义的来源。此外，`@Configuration`类允许通过调用`@Bean`同一类中的其他方法来定义 bean 间的依赖关系。最简单的`@Configuration`类如下所示：

```java
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
```

前面的`AppConfig`类等价于下面的 Spring `<beans/>`XML：

```xml
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
```

完整的@Configuration 与“精简”@Bean 模式？

> 当`@Bean`方法在没有用`@Configuration` 注解的类中声明时 ，它们被称为以“精简”模式处理。在一个或什至在一个普通的旧类中声明的 Bean 方法`@Component`被认为是“精简版”，包含类的不同主要目的和一种`@Bean`方法在那里是一种奖励。例如，服务组件可以通过`@Bean`每个适用组件类上的附加方法向容器公开管理视图。在这种情况下，`@Bean`方法是一种通用的工厂方法机制。
>
> 与全配置`@Configuration` 不同，轻量的`@Bean`方法不能声明 bean 间的依赖关系。相反，它们对其包含组件的内部状态进行操作，并且可以选择对它们可能声明的参数进行操作。因此，`@Bean`方法不应调用其他 `@Bean`方法。每个这样的方法实际上只是特定 bean 引用的工厂方法，没有任何特殊的运行时语义。这里的积极副作用是在运行时不必应用 CGLIB 子类化，因此在类设计方面没有限制（即包含类可能是`final`等等）。
>
> 在常见情况下，`@Bean`方法将在`@Configuration`类中声明，确保始终使用“完整”模式，并且跨方法引用因此被重定向到容器的生命周期管理。这可以防止 `@Bean`通过常规 Java 调用意外调用相同的方法，这有助于减少在“精简”模式下操作时难以追踪的细微错误。

以下部分将深入讨论`@Bean`和`@Configuration`注解。然而，首先，我们介绍了使用基于 Java 的配置创建 Spring 容器的各种方法。

**1.12.2. 通过使用`AnnotationConfigApplicationContext`实例化 Spring 容器**

以下部分记录了 Spring 3.0 中引入的 Spring `AnnotationConfigApplicationContext`。这种通用`ApplicationContext`的实现不仅能够接受 `@Configuration`类作为输入，还能够接受普通`@Component`类和使用 JSR-330 元数据注解的类。

当`@Configuration`类作为输入提供时，`@Configuration`类本身被注册为 bean 定义，并且`@Bean`类中所有声明的方法也被注册为 bean 定义。

当`@Component`和 JSR-330 类被提供时，它们被注册为 bean 定义，并且假定 DI 元数据，如`@Autowired`或`@Inject`在必要时在这些类中使用。

**简单的构造**

与实例化 `ClassPathXmlApplicationContext` 时使用 Spring XML 文件作为输入的方式大致相同，您可以在实例化`AnnotationConfigApplicationContext`时使用 @Configuration 类作为输入如以下示例所示：

```java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

如前所述，`AnnotationConfigApplicationContext`不仅限于使用`@Configuration`类。任何`@Component`或 JSR-330 注解类都可以作为输入提供给构造函数，如以下示例所示：

```java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

前面的示例假定`MyServiceImpl`、`Dependency1`和`Dependency2`使用 Spring 依赖注入注解，例如`@Autowired`.

**通过使用`register(Class<?>…)`以编程方式构建容器**

您可以使用无参数构造函数实例化一个`AnnotationConfigApplicationContext`，然后使用`register()`方法对其进行配置。这种方法在以编程方式构建`AnnotationConfigApplicationContext`. 以下示例显示了如何执行此操作：

```java
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
```

**通过`scan(String…)`启用组件扫描**

要启用组件扫描，您可以如下注解您的`@Configuration`类：

```java
@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    // ...
}
```

有经验的 Spring 用户可能熟悉 Spring 的 `context:namespace` 中的等效 XML 声明，如下例所示：：

```xml
<beans>
    <context:component-scan base-package="com.acme"/>
</beans>
```

在前面的示例中，扫描包`com.acme`以查找任何 带`@Component`注解的类，并且这些类在容器中注册为 Spring bean 定义。`AnnotationConfigApplicationContext`暴露了`scan(String…)`方法以允许相同的组件扫描功能，如以下示例所示：

```java
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
```

请记住，`@Configuration`类是用[元注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-meta-annotations) `@Component`的，因此它们是组件扫描的候选对象。在前面的示例中，假设`AppConfig`在`com.acme`包（或下面的任何包）中声明了 ，在调用`scan()`. 在 `refresh()`之后，它的所有`@Bean` 方法都被处理并注册为容器中的 bean 定义。

**使用`AnnotationConfigWebApplicationContext`支持 Web 应用程序**

`WebApplicationContext`的变体`AnnotationConfigApplicationContext`可用于`AnnotationConfigWebApplicationContext`. 您可以在配置 Spring `ContextLoaderListener`servlet 侦听器、Spring MVC `DispatcherServlet`等时使用此实现。以下`web.xml`代码片段配置了一个典型的 Spring MVC Web 应用程序（注意使用`contextClass`的context-param 和 init-param）：

```xml
<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>
```

对于编程用例， `GenericWebApplicationContext`可以用作`AnnotationConfigWebApplicationContext`. 有关详细信息，请参阅 [`GenericWebApplicationContext`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/web/context/support/GenericWebApplicationContext.html) javadoc。

**1.12.3. 使用`@Bean`注解**

`@Bean`是方法级别的注解，是 XML`<bean/>`元素的直接模拟。注解支持 提供的一些属性`<bean/>`，例如：

* [初始化方法](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean)
* [销毁方法](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-disposablebean)
* [自动装配](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire)
* `name`.

您可以在`@Configuration`-annotated 或 `@Component`-annotated 类中使用`@Bean`注解。

**声明一个 Bean**

要声明一个 bean，你可以用注解来注解一个方法`@Bean`。您可以使用此方法在指定为方法返回值的类型中向`ApplicationContext`注册 bean 定义。默认情况下，bean 名称与方法名称相同。以下示例显示了一个`@Bean`方法声明：

```java
@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
```

前面的配置完全等价于下面的 Spring XML：

```xml
<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
```

这两个声明都使`ApplicationContext` bean中的可用`transferService` bean绑定到`TransferServiceImpl` type 的对象实例，如以下文本图像所示：

```
transferService -> com.acme.TransferServiceImpl
```

您还可以使用默认方法来定义 bean。这允许通过在默认方法上实现带有 bean 定义的接口来组合 bean 配置。

```java
public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfig {

}
```

您还可以使用接口（或基类）返回类型声明您的`@Bean`方法，如以下示例所示：

```java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
```

但是，这会将高级类型预测的可见性限制为指定的接口类型 ( `TransferService`)。然后，只有在实例化受影响的单例 bean 后，容器才知道完整类型 ( `TransferServiceImpl`)。非惰性单例 bean 会根据它们的声明顺序进行实例化，因此您可能会看到不同的类型匹配结果，具体取决于另一个组件何时尝试通过未声明的类型进行匹配（例如`@Autowired TransferServiceImpl`，仅在`transferService`bean 被实例化后才解析）。

如果您始终通过声明的服务接口引用您的类型，则您的 `@Bean`返回类型可以安全地加入该设计决策。但是，对于实现多个接口的组件或可能由其实现类型引用的组件，声明最具体的返回类型可能更安全（至少与引用您的 bean 的注入点所要求的一样具体）。

**Bean 依赖项**

`@Bean`-annotated 方法可以具有任意数量的参数，这些参数描述了构建该 bean 所需的依赖项。例如，如果我们`TransferService` 需要一个`AccountRepository`，我们可以使用方法参数实现该依赖项，如下例所示：

```java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
```

解析机制与基于构造函数的依赖注入几乎相同。有关详细信息，请参阅[相关部分。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-injection)

**接收生命周期回调**

使用`@Bean`注解定义的任何类都支持常规生命周期回调，并且可以使用 JSR-250 中的`@PostConstruct`和`@PreDestroy`注解。有关详细信息，请参阅 [JSR-250 注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-postconstruct-and-predestroy-annotations)。

也完全支持常规的 Spring[生命周期回调。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-nature)如果 bean 实现`InitializingBean`、`DisposableBean`或`Lifecycle`，则容器调用它们各自的方法。

还完全支持标准的`*Aware`接口集（例如[BeanFactoryAware](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beanfactory)、 [BeanNameAware](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware)、 [MessageSourceAware](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-messagesource)、 [ApplicationContextAware](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware)等）。

`@Bean`注解支持指定任意初始化和销毁回调方法，很像 Spring XML`init-method`和`bean`元素上的`destroy-method`属性，如以下示例所示：

```java
public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
```

默认情况下，使用 Java 配置定义的具有公共`close`或`shutdown` 方法的 bean 会自动加入销毁回调。如果您有一个公共 `close`或`shutdown`方法并且您不希望在容器关闭时调用它，您可以添加`@Bean(destroyMethod="")`到您的 bean 定义以禁用默认`(inferred)`模式。默认情况下，您可能希望对使用 JNDI 获取的资源执行此操作，因为它的生命周期在应用程序之外进行管理。特别是，请确保始终为`DataSource`.以下示例显示了如何防止 a 的自动销毁回调 `DataSource`：

```java
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}
```

此外，对于`@Bean`方法，您通常使用程序化 JNDI 查找，通过使用 Spring`JndiTemplate`或`JndiLocatorDelegate`帮助程序或直接使用 JNDI `InitialContext`但不使用`JndiObjectFactoryBean`变体（这将迫使您将返回类型声明为`FactoryBean`类型而不是实际的目标类型，从而更难用于其他`@Bean`方法中的交叉引用调用，这些方法旨在引用此处提供的资源）。

在上述示例`BeanOne`的情况下，在构造过程中直接调用该`init()` 方法同样有效，如下例所示：

```java
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}
```

当您直接在 Java 中工作时，您可以对您的对象做任何您喜欢的事情，而不必总是依赖容器生命周期。

**指定 Bean 范围**

Spring 包含`@Scope`注解，以便您可以指定 bean 的范围。

**使用`@Scope`注解**

您可以指定使用`@Bean`注解定义的 bean 应具有特定范围。您可以使用 [Bean Scopes](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes)部分中指定的任何标准范围。

默认范围是`singleton`，但您可以使用`@Scope`注解覆盖它，如以下示例所示：

```java
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
```

**`@Scope`和`scoped-proxy`**

Spring 提供了一种通过 [作用域代理](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other-injection)处理作用域依赖的便捷方式。使用 XML 配置时创建此类代理的最简单方法是`<aop:scoped-proxy/>`元素。使用注解在 Java 中配置您的 bean提供了对属性`@Scope`的等效支持。`proxyMode`默认值为`ScopedProxyMode.DEFAULT`，这通常表示不应创建作用域代理，除非在组件扫描指令级别配置了不同的默认值。您可以 指定`ScopedProxyMode.TARGET_CLASS`、`ScopedProxyMode.INTERFACES`或`ScopedProxyMode.NO`。

如果您将 XML 参考文档中的作用域代理示例（请参阅 [作用域代理](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other-injection)）移植到我们使用Java的`@Bean` ，它类似于以下内容：

```java
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
```

**自定义 Bean 命名**

默认情况下，配置类使用`@Bean`方法的名称作为生成的 bean 的名称。但是，可以使用`name`属性覆盖此功能，如以下示例所示：

```java
@Configuration
public class AppConfig {

    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}
```

**Bean 别名**

正如[命名 Bean](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beanname)中所讨论的，有时需要为单个 bean 提供多个名称，也称为 bean 别名。注解`@Bean`的`name`属性 为此目的接受一个字符串数组。以下示例显示了如何为 bean 设置多个别名：

```java
@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}
```

**Bean描述**

有时，提供更详细的 bean 文本描述会很有帮助。当 bean 被暴露（可能通过 JMX）用于监视目的时，这可能特别有用。

要向`@Bean` 添加描述，您可以使用 [`@Description`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/Description.html) 注解，如以下示例所示：

```java
@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}
```

**1.12.4. 使用`@Configuration`注解**

`@Configuration`是一个类级别的注解，表明一个对象是 bean 定义的来源。`@Configuration`类通过`@Configuration`-annotated 方法声明 bean 。对`@Configuration`类上的方法的`@Bean`调用也可用于定义 bean 间的依赖关系。请参阅[基本概念：`@Bean`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-basic-concepts)[和](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-basic-concepts)`@Configuration`一般介绍。

**注入 内部bean 依赖**

当 bean 相互依赖时，表达这种依赖关系就像让一个 bean 方法调用另一个方法一样简单，如以下示例所示：

```java
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
```

在前面的示例中，通过构造函数注入`beanOne`接收对`beanTwo`的引用。

这种声明 bean 间依赖关系的方法仅在`@Bean`方法在`@Configuration`类中声明时才有效。您不能使用普通`@Component`类来声明 bean 间的依赖关系。

**查找方法注入**

如前所述，[查找方法注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-method-injection)是您应该很少使用的高级功能。在单例范围的 bean 依赖于原型范围的 bean 的情况下，它很有用。使用 Java 进行这种类型的配置为实现这种模式提供了一种自然的方式。下面的例子展示了如何使用查找方法注入：

```java
public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
```

通过使用 Java 配置，您可以创建一个子类，`CommandManager`中抽象`createCommand()`方法被覆盖，从而查找新的（原型）命令对象。以下示例显示了如何执行此操作：

```java
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
```

**有关基于 Java 的配置如何在内部工作的更多信息**

考虑下面的例子，它显示了一个被`@Bean`注解的方法被调用了两次：

```java
@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}
```

`clientDao()`已被`clientService1()`调用一次和`clientService2()`调用一次。由于此方法会创建一个新`ClientDaoImpl`实例并返回它，因此您通常会期望有两个实例（每个服务一个实例）。那肯定会有问题：在 Spring 中，实例化的 bean默认有一个`singleton`作用域。这就是神奇之处：所有`@Configuration`类在启动时都使用`CGLIB`. 在子类中，子方法在调用父方法并创建新实例之前，首先检查容器中是否有任何缓存（作用域）bean。

根据 bean 的范围，行为可能会有所不同。我们在这里谈论单例。

从 Spring 3.2 开始，不再需要将 CGLIB 添加到类路径中，因为 CGLIB 类已被重新打包`org.springframework.cglib`并直接包含在 spring-core JAR 中。

由于 CGLIB 在启动时动态添加功能，因此存在一些限制。特别是，配置类不能是最终的。但是，从 4.3 开始，配置类上允许使用任何构造函数，包括使用 `@Autowired`或使用单个非默认构造函数声明进行默认注入。如果您希望避免任何 CGLIB 强加的限制，请考虑 在非`@Configuration`类上声明您的`@Bean`方法（例如，改为在普通`@Component`类上）。方法之间的跨方法调用`@Bean`不会被拦截，因此您必须完全依赖构造函数或方法级别的依赖注入。

**1.12.5. 组合基于 Java 的配置**

Spring 的基于 Java 的配置功能允许您编写注解，这可以降低配置的复杂性。

**使用`@Import`注解**

就像`<import/>`在 Spring XML 文件中使用该元素来帮助模块化配置一样，`@Import`注解允许`@Bean`从另一个配置类加载定义，如以下示例所示：

```java
@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
```

现在，不需要同时指定`ConfigA.class`和`ConfigB.class`. 在实例化上下文时，只需要显式提供`ConfigB`，如以下示例所示：

```java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
```

这种方法简化了容器的实例化，因为只需要处理一个类，而不是要求您 `@Configuration`在构造过程中记住大量潜在的类。

从 Spring Framework 4.2 开始，`@Import`还支持对常规组件类的引用，类似于`AnnotationConfigApplicationContext.register`方法。如果您想通过使用一些配置类作为入口点来显式定义所有组件来避免组件扫描，这将特别有用。

**`@Bean`注入对导入定义的依赖**

前面的示例有效，但过于简单。在大多数实际场景中，bean 跨配置类相互依赖。使用 XML 时，这不是问题，因为不涉及编译器，您可以声明 `ref="someBean"`并信任 Spring 在容器初始化期间解决它。使用`@Configuration`类时，Java 编译器对配置模型施加约束，因为对其他 bean 的引用必须是有效的 Java 语法。

幸运的是，解决这个问题很简单。正如[我们已经讨论过](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-dependencies)的，一个`@Bean`方法可以有任意数量的参数来描述 bean 的依赖关系。考虑以下具有多个`@Configuration` 类的更真实的场景，每个类都依赖于其他类中声明的 bean：

```java
@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

还有另一种方法可以达到相同的结果。请记住，`@Configuration`类最终只是容器中的另一个 bean：这意味着它们可以像任何其他 bean 一样利用`@Autowired`和`@Value`注入其他特性。

确保您以这种方式注入的依赖项只是最简单的类型。`@Configuration` 类在上下文初始化期间很早就被处理，并且强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能使用基于参数的注入，如前面的示例所示。

此外，请特别注意`BeanPostProcessor`和的`BeanFactoryPostProcessor`定义`@Bean`。这些通常应该被声明为`static @Bean`方法，而不是触发它们包含的配置类的实例化。否则，`@Autowired`和`@Value`可能无法在配置类本身上工作，因为可以将其创建为早于 [`AutowiredAnnotationBeanPostProcessor`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html).

以下示例显示了如何将一个 bean 自动装配到另一个 bean：

```java
@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

仅从 Spring Framework 4.3 开始支持`@Configuration`类中的 构造函数注入。另请注意，如果目标 bean 仅定义一个构造函数 ，则无需指定`@Autowired`。

完全合格的导入Bean，便于导航

在前面的场景中，使用`@Autowired`运行良好并提供了所需的模块化，但确定自动装配 bean 定义的确切声明位置仍然有些模棱两可。例如，作为开发人员，查找`ServiceConfig`，您如何知道`@Autowired AccountRepository`bean 的确切声明位置？它在代码中并不明确，这可能还好。请记住， [Eclipse 的 Spring Tools](https://spring.io/tools)提供的工具可以渲染显示所有连接方式的图形，这可能就是您所需要的。此外，您的 Java IDE 可以轻松找到`AccountRepository`类型的所有声明和使用，并快速向您显示返回该类型的`@Bean`方法的位置。

如果这种歧义是不可接受的，并且您希望在 IDE 中从一个`@Configuration`类直接导航到另一个类，请考虑自动装配配置类本身。以下示例显示了如何执行此操作：

```java
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
```

在上述情况下，where `AccountRepository`is defined 是完全明确的。但是，`ServiceConfig`现在与`RepositoryConfig`. 这就是权衡。通过使用基于接口或基于抽象类的类可以在一定程度上缓解这种紧密耦合`@Configuration`。考虑以下示例：

```java
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
```

相对于具体的`DefaultRepositoryConfig`， `ServiceConfig` 是松散耦合的 ，并且内置的 IDE 工具仍然有用：您可以轻松获得`RepositoryConfig`实现的类型层次结构。通过这种方式，导航`@Configuration`类及其依赖项与导航基于接口的代码的通常过程没有什么不同。

如果您想影响某些 bean 的启动创建顺序，请考虑将其中一些声明为`@Lazy`（用于在首次访问时创建而不是在启动时创建）或`@DependsOn`某些其他 bean（确保在当前 bean 之前创建特定的其他 bean，超出后者的直接依赖意味着什么）。

**有条件地包含`@Configuration`类或`@Bean`方法**

基于某些任意系统状态，有条件地启用或禁用完整的`@Configuration`类甚至单个`@Bean`方法通常很有用。一个常见的例子是，只有在 Spring 中启用了特定配置文件时才使用`@Profile`注解来激活 bean `Environment`（有关详细信息，请参阅[Bean 定义配置文件](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition-profiles) ）。

`@Profile`注解实际上是通过使用更灵活的注解来实现的，称为[`@Conditional`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/Conditional.html). `@Conditional`注解指示 在注册`@Bean`之前应参考`org.springframework.context.annotation.Condition`的具体实现。

接口的实现`Condition`提供了一个`matches(…)` 返回`true`或的方法`false`。例如，以下清单显示了 `Condition`用于`@Profile` 的实际实现：

```java
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}
```

有关更多详细信息，请参阅[`@Conditional`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/Conditional.html) javadoc。

**结合 Java 和 XML 配置**

Spring 的`@Configuration`类支持并非旨在 100% 完全替代 Spring XML。一些工具，例如 Spring XML 命名空间，仍然是配置容器的理想方式。在 XML 方便或必要的情况下，您可以选择：或者以“以 XML 为中心”的方式实例化容器，例如，`ClassPathXmlApplicationContext`，或者通过使用 `AnnotationConfigApplicationContext` 和 `@ImportResource` 注释以“以 Java 为中心”的方式实例化它，以根据需要导入 XML。

**以 XML 为中心的`@Configuration`类的使用**

最好从 XML 引导 Spring 容器并 以特别的方式包含`@Configuration`类。例如，在使用 Spring XML 的大型现有代码库中，更容易根据需要创建`@Configuration`类并从现有 XML 文件中包含它们。在本节的后面部分，我们将介绍在这种“以 XML 为中心”的情况下使用`@Configuration`类的选项。

将类声明`@Configuration`为普通 Spring`<bean/>`元素

请记住，`@Configuration`类最终是容器中的 bean 定义。在本系列示例中，我们创建了一个名为`AppConfig`的`@Configuration`类，并将其`system-test-config.xml`作为`<bean/>`定义包含在其中。因为 `<context:annotation-config/>`是开启的，所以容器会识别 `@Configuration`注解并正确处理`AppConfig`其中`@Bean`声明的方法 。

以下示例显示了 Java 中的一个普通配置类：

```java
@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
```

以下示例显示了示例`system-test-config.xml`文件的一部分：

```xml
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
```

以下示例显示了一个可能的`jdbc.properties`文件：

```properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
```

```java
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
```

在`system-test-config.xml`文件中，`AppConfig` `<bean/>`不声明`id` 元素。虽然这样做是可以接受的，但这是不必要的，因为没有其他 bean 曾经引用过它，并且不太可能通过名称从容器中显式获取。类似地，`DataSource`bean 仅按类型自动装配，因此并不严格要求显式 bean `id` 。

使用 `<context:component-scan/>` 拾取`@Configuration`类

因为`@Configuration`是用 `@Component`元注解的，带`@Configuration`注解的类自动成为组件扫描的候选对象。使用与前面示例中描述的相同场景，我们可以重新定义`system-test-config.xml`以利用组件扫描。请注意，在这种情况下，我们不需要显式声明 `<context:annotation-config/>`，因为`<context:component-scan/>`启用了相同的功能。

以下示例显示了修改后的`system-test-config.xml`文件：

```xml
<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
```

**`@Configuration`以类为中心使用 XML`@ImportResource`**

在`@Configuration`类是配置容器的主要机制的应用程序中，仍然可能至少需要使用一些 XML。在这些场景中，您可以根据`@ImportResource`需要使用和定义尽可能多的 XML。这样做实现了一种“以 Java 为中心”的方法来配置容器并将 XML 保持在最低限度。以下示例（包括配置类、定义 bean 的 XML 文件、属性文件和`main`类）显示了如何使用`@ImportResource`注解来实现“以 Java 为中心”的配置，该配置根据需要使用 XML：

```java
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
 
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.密码=
```

```java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
```

#### 1.13. 抽象环境

[`Environment`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/env/Environment.html)接口是集成在容器中的抽象，它对应用程序环境的两个关键方面进行建模：配置[文件](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition-profiles) 和[属性](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-property-source-abstraction)。

配置文件是一个命名的、逻辑的 bean 定义组，仅当给定的配置文件处于活动状态时才向容器注册。可以将 Bean 分配给配置文件，无论是在 XML 中定义还是使用注解定义。与配置文件相关的`Environment`对象的作用是确定哪些配置文件（如果有）当前处于活动状态，以及哪些配置文件（如果有）默认情况下应该是活动的。

属性在几乎所有应用程序中都发挥着重要作用，并且可能源自多种来源：属性文件、JVM 系统属性、系统环境变量、JNDI、servlet 上下文参数、ad-hoc`Properties`对象、`Map`对象等。与属性相关的`Environment`对象的作用是为用户提供一个方便的服务接口，用于配置属性源并从中解析属性。

**1.13.1. Bean 定义配置文件**

Bean 定义配置文件在核心容器中提供了一种机制，允许在不同环境中注册不同的 bean。“环境”这个词对不同的用户可能意味着不同的东西，这个功能可以帮助许多用例，包括：

* 在开发中处理内存中的数据源，而不是在 QA 或生产中从 JNDI 中查找相同的数据源。
* 仅在将应用程序部署到性能环境时才注册监控基础架构。
* 为客户 A 和客户 B 部署注册定制的 bean 实现。

考虑实际应用程序中需要 `DataSource`. 在测试环境中，配置可能类似于以下内容：

```java
@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}
```

现在考虑如何将此应用程序部署到 QA 或生产环境中，假设应用程序的数据源已在生产应用程序服务器的 JNDI 目录中注册。我们的`dataSource`bean 现在看起来像下面的清单：

```java
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
```

问题是如何根据当前环境在使用这两种变体之间进行切换。随着时间的推移，Spring 用户设计了许多方法来完成此任务，通常依赖于系统环境变量和包含`<import/>`标签的 XML 语句的组合，`${placeholder}`根据环境变量的值解析为正确的配置文件路径。Bean 定义概要文件是一个核心容器特性，它为这个问题提供了解决方案。

如果我们概括前面环境特定 bean 定义示例中所示的用例，我们最终需要在某些上下文中注册某些 bean 定义，但在其他上下文中不需要。您可以说您想在情况 A 中注册特定的 bean 定义配置文件，在情况 B 中注册不同的配置文件。我们首先更新配置以反映这种需求。

**使用`@Profile`**

当一个或多个指定的配置文件处于活动状态时，[`@Profile`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/Profile.html) 注解可让您指示组件有资格注册。使用我们前面的示例，我们可以重写`dataSource`配置如下：

```java
@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
```

```java
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
```

如前所述，对于`@Bean`方法，您通常选择使用程序化 JNDI 查找，通过使用 Spring 的`JndiTemplate`/`JndiLocatorDelegate`助手或`InitialContext`前面显示的直接 JNDI 用法，而不是`JndiObjectFactoryBean` 变体，这将迫使您将返回类型声明为`FactoryBean`类型。

配置文件字符串可能包含一个简单的配置文件名称（例如，`production`）或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑（例如，`production & us-east`）。配置文件表达式中支持以下运算符：

* `!`：配置文件的逻辑“非”
* `&`：配置文件的逻辑“与”
* `|`：配置文件的逻辑“或”

不能在不使用括号的情况下混合使用`&`和`|`运算符。例如， `production & us-east | eu-central`不是一个有效的表达式。它必须表示为 `production & (us-east | eu-central)`。

您可以将`@Profile`其用作[元注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-meta-annotations)以创建自定义组合注解。以下示例定义了一个自定义 `@Production`注解，您可以将其用作 `@Profile("production")`的替代品 ：

```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
```

如果一个`@Configuration`类用`@Profile` 标记，则与该类关联的所有`@Bean`方法和 `@Import`注解都将被绕过，除非一个或多个指定的配置文件处于活动状态。如果一个`@Component`或`@Configuration`类标有`@Profile({"p1", "p2"})`，则除非已激活配置文件“p1”或“p2”，否则不会注册或处理该类。如果给定配置文件以 NOT 运算符 (`!` ) 为前缀，则仅当配置文件不活动时才注册带注解的元素。例如，`@Profile({"p1", "!p2"})`如果配置文件“p1”处于活动状态或配置文件“p2”未处于活动状态，则会发生注册。

`@Profile`也可以在方法级别声明为仅包含配置类的一个特定 bean（例如，对于特定 bean 的替代变体），如以下示例所示：

```java
@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") 
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production") 
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
```

`@Bean`方法使用`@Profile`，可能会应用一种特殊情况：在相同 Java 方法名称的重载`@Bean`方法的情况下（类似于构造函数重载），需要在所有重载方法上一致地声明一个`@Profile`条件。如果条件不一致，则仅重载方法中第一个声明的条件。因此，`@Profile`不能用于选择具有特定参数签名的重载方法而不选择另一个。同一 bean 的所有工厂方法之间的解析在创建时遵循 Spring 的构造函数解析算法。如果要定义具有不同配置文件条件的替代 bean，请使用不同的 Java 方法名称，这些`@Bean`方法名称通过使用name 属性指向相同的 bean 名称，如前面的示例所示。如果参数签名都相同（例如，所有变体都有无参数工厂方法），这是首先在有效 Java 类中表示这种安排的唯一方法（因为只能有一个特定名称和参数签名的方法）。

**XML Bean 定义配置文件**

XML 对应物是`<beans>`元素的`profile`属性。我们前面的示例配置可以重写为两个 XML 文件，如下所示：

```xml
<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
```

也可以避免在同一文件中拆分和嵌套`<beans/>`元素，如以下示例所示：

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>
```

XML 对应项不支持前面描述的配置文件表达式。但是，可以使用`!`运算符来否定配置文件。也可以通过嵌套配置文件来应用逻辑“和”，如以下示例所示：

```xml
<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:jdbc="http://www.springframework.org/schema/jdbc"    xmlns:jee="http://www.springframework.org/schema/jee"    xsi:schemaLocation="...">     <!-- other bean definitions -->
	<beans profile="production"> 
		<beans profile="us-east"> 
			<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>         </beans>  
	</beans> 
</beans>
```

在前面的示例中，如果`production`和 `us-east` 配置文件都处于活动状态，则公开 `dataSource`bean 。

**激活配置文件**

现在我们已经更新了配置，我们仍然需要指示 Spring 哪个配置文件处于活动状态。如果我们现在启动示例应用程序，我们会看到抛出`NoSuchBeanDefinitionException`异常，因为容器找不到名为`dataSource` 的 Spring bean 。

激活配置文件可以通过多种方式完成，但最直接的方法是以编程方式针对通过 `ApplicationContext` 提供的`Environment` API 来执行此操作。. 以下示例显示了如何执行此操作：

```java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
```

此外，您还可以通过 `spring.profiles.active`属性以声明方式激活配置文件，可以通过系统环境变量、JVM 系统属性、`web.xml` servlet 上下文参数来指定，甚至可以作为 JNDI 中的条目（参见[`PropertySource`Abstraction](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-property-source-abstraction)）。在集成测试中，可以使用`spring-test` 模块中的`@ActiveProfiles`注解来声明活动配置文件（请参阅环境配置文件的[上下文配置](https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-ctx-management-env-profiles)）。

请注意，配置文件不是“非此即彼”的命题。您可以一次激活多个配置文件。以编程方式，您可以为 `setActiveProfiles()`接受`String…`可变参数的方法提供多个配置文件名称。以下示例激活多个配置文件：

```java
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
```

以声明方式，`spring.profiles.active`可以接受以逗号分隔的配置文件名称列表，如以下示例所示：

```
    -Dspring.profiles.active="profile1,profile2"
```

**默认配置文件**

默认配置文件表示默认启用的配置文件。考虑以下示例：

```java
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
```

如果没有激活的配置文件，`dataSource`则创建。您可以将此视为一种为一个或多个 bean 提供默认定义的方法。如果启用了任何配置文件，则默认配置文件不适用。

您可以使用`Environment` 的`setDefaultProfiles()`方法或以声明方式使用`spring.profiles.default`属性来更改默认配置文件的名称。

**1.13.2.`PropertySource`抽象**

Spring 的`Environment`抽象提供了对属性源的可配置层次结构的搜索操作。考虑以下清单：

```java
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
```

在前面的`my-property`代码片段中，我们看到了一种询问 Spring 是否为当前环境定义属性的高级方法。为了回答这个问题，`Environment`对象对一组对象执行搜索[`PropertySource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/env/PropertySource.html) 。`PropertySource`是对任何键值对源的简单抽象，Spring[`StandardEnvironment`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/env/StandardEnvironment.html) 配置了两个 PropertySource 对象——一个代表 JVM 系统属性集（`System.getProperties()`），一个代表系统环境变量集（`System.getenv()`）。

这些默认属性源用于`StandardEnvironment`, 用于独立应用程序。[`StandardServletEnvironment`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/web/context/support/StandardServletEnvironment.html) 填充了其他默认属性源，包括 servlet 配置和 servlet 上下文参数。它可以选择启用[`JndiPropertySource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/jndi/JndiPropertySource.html). 有关详细信息，请参阅 javadoc。

具体来说，当您使用`my-property` 时，如果系统属性或环境变量在运行时存在`my-property`，则调用`StandardEnvironment`的`env.containsProperty("my-property")` 返回 true 。

执行的搜索是分层的。默认情况下，系统属性优先于环境变量。因此，如果在调用`env.getProperty("my-property")` 期间恰好在两个位置都设置了`my-property`属性，则系统属性值“获胜”并返回。请注意，属性值不会合并，而是完全被前面的条目覆盖。对于 common `StandardServletEnvironment`，完整的层次结构如下，最高优先级的条目位于顶部：

1. ServletConfig 参数（如果适用——例如，在`DispatcherServlet`上下文的情况下）
2. ServletContext 参数（web.xml 上下文参数条目）
3. JNDI 环境变量（`java:comp/env/`条目）
4. JVM 系统属性（`-D`命令行参数）
5. JVM系统环境（操作系统环境变量）

最重要的是，整个机制是可配置的。也许您有一个想要集成到此搜索中的自定义属性源。为此，请实现并实例化您自己的`PropertySource`并将其添加到当前`Environment`的`PropertySources`集合中. 以下示例显示了如何执行此操作：

```java
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
```

在前面的代码中，`MyPropertySource`已在搜索中以最高优先级添加。如果它包含一个`my-property`属性，则检测并返回该属性，以支持`my-property`任何其他 `PropertySource`中的任何属性。[`MutablePropertySources`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/env/MutablePropertySources.html) API 公开了许多允许精确操作属性源集的方法。

**1.13.3. 使用`@PropertySource`**

[`@PropertySource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/annotation/PropertySource.html) 注解提供了一种方便且声明性的机制，用于将 `PropertySource` 添加到Spring 的`Environment`.

给定一个包含键值对的名为`testbean.name=myTestBean`的`app.properties`文件，以下`@Configuration`类使用`@PropertySource`以调用`testBean.getName()`返回`myTestBean`：

```java
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
```

资源位置中存在的任何`${…}`占位符都会`@PropertySource`针对已针对环境注册的属性源集进行解析，如以下示例所示：

```java
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
```

假设`my.placeholder`存在于已注册的属性源之一（例如，系统属性或环境变量）中，则占位符被解析为相应的值。如果不是，则将`default/path`其用作默认值。如果未指定默认值且无法解析属性， 则抛出 `IllegalArgumentException`。

根据Java 8 约定，`@PropertySource`注解是可重复的。但是，所有此类`@PropertySource`注解都需要在同一级别声明，或者直接在配置类上声明，或者作为同一自定义注解中的元注解。不建议混合直接注解和元注解，因为直接注解有效地覆盖了元注解。

**1.13.4. 语句中的占位符解析**

从历史上看，元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。这已不再是这种情况。因为抽象`Environment`是在整个容器中集成的，所以很容易通过它来路由占位符的解析。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级或完全删除它们。您还可以根据需要将自己的属性源添加到组合中。

具体来说，无论属性在何处定义`customer`，只要它在 `Environment`中可用，以下语句都有效：

```xml
<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
```

#### 1.14. 注册一个`LoadTimeWeaver`

当`LoadTimeWeaver`类加载到 Java 虚拟机 (JVM) 中时，Spring 使用它来动态转换类。

要启用加载时编织，您可以将`@EnableLoadTimeWeaving` 添加到您的 `@Configuration`类之一，如以下示例所示：

```java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
```

或者，对于 XML 配置，您可以使用以下`context:load-time-weaver`元素：

```xml
<beans>
    <context:load-time-weaver/>
</beans>
```

一旦为`ApplicationContext` 配置 ，`ApplicationContext`中的任何 bean 都 可以实现`LoadTimeWeaverAware`，从而接收对加载时编织器实例的引用。[这在与Spring 的 JPA 支持](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#orm-jpa)结合使用时特别有用， 其中 JPA 类转换可能需要加载时编织。有关更多详细信息，请参阅 [`LocalContainerEntityManagerFactoryBean`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html) javadoc。有关 AspectJ 加载时编织的更多信息，请参阅[Spring Framework 中使用 AspectJ 进行加载时编织](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-aj-ltw)。

#### 1.15.`ApplicationContext`的附加功能

正如在[介绍章节](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans)中所讨论的，该`org.springframework.beans.factory` 包提供了管理和操作 bean 的基本功能，包括以编程方式。该`org.springframework.context`包添加了 [`ApplicationContext`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/ApplicationContext.html) 接口，它扩展了`BeanFactory`接口，此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。许多人以完全声明`ApplicationContext`的方式使用它，甚至没有以编程方式创建它，而是依赖于支持类，例如在Java EE Web 应用程序的正常启动过程中`ContextLoader`自动实例化一个 `ApplicationContext`。

`BeanFactory`为了以更加面向框架的风格增强功能，上下文包还提供了以下功能：

* 通过`MessageSource`访问 i18n 风格的消息。
* 通过`ResourceLoader`接口访问资源，例如 URL 和文件。
* `ApplicationListener`事件发布，即通过使用`ApplicationEventPublisher接口发布给实现接口的bean` 。
* 加载多个（分层）上下文，让每个上下文都通过`HierarchicalBeanFactory`接口专注于一个特定的层，例如应用程序的 Web 层 。

**1.15.1. 国际化使用`MessageSource`**

`ApplicationContext`接口扩展了一个名为`MessageSource`的接口，因此提供了国际化（“i18n”）功能。Spring 还提供了 `HierarchicalMessageSource`接口，可以分层解析消息。这些接口共同提供了 Spring 影响消息解析的基础。这些接口上定义的方法包括：

* `String getMessage(String code, Object[] args, String default, Locale loc)`: 用于从`MessageSource`获取消息. 如果未找到指定语言环境的消息，则使用默认消息。使用标准库`MessageFormat`提供的功能，传入的任何参数都将成为替换值。
* `String getMessage(String code, Object[] args, Locale loc)`：与上一种方法基本相同，但有一个区别：不能指定默认消息。如果找不到消息，`NoSuchMessageException`则抛出 a。
* `String getMessage(MessageSourceResolvable resolvable, Locale locale)`: 上述方法中使用的所有属性也都包装在一个名为 的类 `MessageSourceResolvable`中，您可以在此方法中使用该类。

加载`ApplicationContext`时，它会自动搜索上下文中定义的`MessageSource` bean。bean 必须具有`messageSource`名称。如果找到这样的 bean，则对前面方法的所有调用都委托给消息源。如果未找到消息源，则`ApplicationContext`尝试查找包含同名 bean 的父级。如果是这样，它将使用该 bean 作为`MessageSource`. 如果 `ApplicationContext`找不到任何消息源，则实例化一个空 `DelegatingMessageSource`，以便能够接受对上述方法的调用。

Spring 提供了三个`MessageSource`实现`ResourceBundleMessageSource`，`ReloadableResourceBundleMessageSource` 和`StaticMessageSource`。所有这些`HierarchicalMessageSource`都是为了进行嵌套消息传递而实现的。`StaticMessageSource`很少使用，但`ResourceBundleMessageSource`提供了将消息添加到源的编程方式。以下示例显示：

```xml
<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>
```

该示例假定您有三个名为 `format`，`exceptions`和`windows` 的资源包在您的类路径中定义。任何解析消息的请求都以通过`ResourceBundle`对象解析消息的 JDK 标准方式处理。出于示例的目的，假设上述两个资源包文件的内容如下：

```
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
```

下一个示例显示了一个运行该`MessageSource`功能的程序。请记住，所有`ApplicationContext`实现也是`MessageSource` 实现，因此可以转换为`MessageSource`接口。

```java
public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}
```

上述程序的结果输出如下：

```
Alligators rock!
```

总而言之，`MessageSource`是在一个名为 `beans.xml`的文件中定义的，该文件位于类路径的根目录中。`messageSource` bean 定义通过其属性引用了许多资源包。`basenames`列表中传递给`basenames`属性的三个文件作为文件存在于类路径的根目录中，分别称为`format.properties`、`exceptions.properties`和 `windows.properties`。

下一个示例显示传递给消息查找的参数。这些参数被转换为`String`对象并插入到查找消息中的占位符中。

```xml
<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.something.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
```

```java
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", Locale.ENGLISH);
        System.out.println(message);
    }
}
```

调用该`execute()`方法的结果输出如下：

```
The userDao argument is required.
```

关于国际化（“i18n”），Spring 的各种`MessageSource` 实现遵循与标准 JDK 相同的语言环境解析和回退规则 `ResourceBundle`。简而言之，继续前面定义的`messageSource`示例，如果您想根据英国 ( `en-GB`) 语言环境解析消息，您将分别创建名为`format_en_GB.properties`、`exceptions_en_GB.properties`和 `windows_en_GB.properties`的文件。

通常，区域设置解析由应用程序的周围环境管理。在以下示例中，手动指定解析（英国）消息的语言环境：

```
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
```

```java
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}
```

上述程序运行的结果如下：

```
Ebagum lad, the 'userDao' argument is required, I say, required.
```

您还可以使用该`MessageSourceAware`接口来获取对 已定义的任何`MessageSource`内容的引用。在创建和配置 bean 时，在实现 MessageSourceAware 接口的 ApplicationContext 中定义的任何 bean 都会被注入应用程序上下文的 MessageSource。

因为 Spring`MessageSource`是基于 Java 的`ResourceBundle`，所以它不会合并具有相同基本名称的包，而只会使用找到的第一个包。具有相同基本名称的后续消息包将被忽略。

作为 `ResourceBundleMessageSource`的替代方案，Spring 提供了一个 `ReloadableResourceBundleMessageSource`类。此变体支持相同的捆绑文件格式，但比基于标准 JDK 的 `ResourceBundleMessageSource`实现更灵活。特别是，它允许从任何 Spring 资源位置（不仅从类路径）读取文件，并支持捆绑属性文件的热重载（同时在它们之间有效地缓存它们）。有关详细信息，请参阅[`ReloadableResourceBundleMessageSource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/support/ReloadableResourceBundleMessageSource.html) javadoc。

**1.15.2. 标准和自定义事件**

中的事件处理`ApplicationContext`是通过`ApplicationEvent` 类和`ApplicationListener`接口提供的。如果将实现 `ApplicationListener`接口的 bean 部署到上下文中，则每次 `ApplicationEvent`发布到 `ApplicationContext`时，都会通知该 bean。本质上，这是标准的观察者设计模式。

从 Spring 4.2 开始，事件基础结构得到了显着改进，并提供了[基于注解的模型](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-events-annotation)以及发布任意事件的能力（即，不一定从 扩展的对象`ApplicationEvent`）。当这样的对象发布时，我们会为您将其包装在一个事件中。

下表描述了 Spring 提供的标准事件：

| 事件                           | 解释                                                                                                                                                                                                                                                                                                       |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ContextRefreshedEvent`      | 在初始化或刷新时发布`ApplicationContext`（例如，通过使用接口`refresh()`上的方法`ConfigurableApplicationContext`）。这里，“初始化”意味着所有 bean 都已加载，后处理器 bean 被检测并激活，单例被预实例化，并且`ApplicationContext`对象已准备好使用。只要上下文没有关闭，就可以多次触发刷新，前提是所选择的`ApplicationContext`实际支持这种“热”刷新。例如，`XmlWebApplicationContext`支持热刷新，但 `GenericApplicationContext`不支持。 |
| `ContextStartedEvent`        | 使用接口上的方法 `ApplicationContext`启动时发布。在这里，“已启动”意味着所有 bean 都接收到一个明确的启动信号。通常，此信号用于在显式停止后重新启动 bean，但它也可用于启动尚未配置为自动启动的组件（例如，尚未在初始化时启动的组件）。`start()``ConfigurableApplicationContext``Lifecycle`                                                                                                                  |
| `ContextStoppedEvent`        | 使用接口上的方法 `ApplicationContext`停止时发布。在这里，“停止”意味着所有 的 bean 都会收到一个明确的停止信号。可以通过 调用重新启动已停止的上下文。`stop()``ConfigurableApplicationContext``Lifecycle``start()`                                                                                                                                                    |
| `ContextClosedEvent`         | 在`ApplicationContext`使用接口`close()`上的方法`ConfigurableApplicationContext`或通过 JVM 关闭挂钩关闭时发布。在这里，“关闭”意味着所有的单例 bean 都将被销毁。一旦上下文关闭，它就到了生命的尽头，无法刷新或重新启动。                                                                                                                                                         |
| `RequestHandledEvent`        | 一个特定于 Web 的事件，告诉所有 bean 一个 HTTP 请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 Web 应用程序`DispatcherServlet`。                                                                                                                                                                                                      |
| `ServletRequestHandledEvent` | 它的子类`RequestHandledEvent`添加了 Servlet 特定的上下文信息。                                                                                                                                                                                                                                                           |

您还可以创建和发布自己的自定义事件。以下示例显示了一个扩展 Spring`ApplicationEvent`基类的简单类：

```java
public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}
```

要发布自定义`ApplicationEvent`，请调用 `ApplicationEventPublisher`的`publishEvent()`. 通常，这是通过创建一个实现 `ApplicationEventPublisherAware`并将其注册为 Spring bean 的类来完成的。下面的例子展示了这样一个类：

```java
public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}
```

在配置时，Spring 容器检测到`EmailService`实现 `ApplicationEventPublisherAware`并自动调用 `setApplicationEventPublisher()`. 实际上，传入的参数是Spring容器本身。您正在通过其 `ApplicationEventPublisher`接口与应用程序上下文进行交互。

要接收自定义的`ApplicationEvent`，您可以创建一个实现 `ApplicationListener`并将其注册为 Spring bean 的类。下面的例子展示了这样一个类：

```java
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
```

请注意，`ApplicationListener`通常使用自定义事件的类型进行参数化（在前面的`BlockedListEvent`示例中）。这意味着该 `onApplicationEvent()`方法可以保持类型安全，避免任何向下转换的需要。您可以根据需要注册任意数量的事件侦听器，但请注意，默认情况下，事件侦听器会同步接收事件。这意味着该`publishEvent()`方法会阻塞，直到所有侦听器都完成了对事件的处理。这种同步和单线程方法的一个优点是，当侦听器接收到事件时，如果事务上下文可用，它会在发布者的事务上下文中运行。如果需要另一种事件发布策略，请参阅 javadoc 了解 Spring 的 [`ApplicationEventMulticaster`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/event/ApplicationEventMulticaster.html)接口和[`SimpleApplicationEventMulticaster`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/event/SimpleApplicationEventMulticaster.html) 配置选项的实现。

以下示例显示了用于注册和配置上述每个类的 bean 定义：

```xml
<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
```

总而言之，当调用`emailService` bean 的`sendEmail()`方法时，如果有任何电子邮件消息应该被阻止， 则会发布一个自定义`BlockedListEvent`类型的事件。`blockedListNotifier`bean 注册为 an `ApplicationListener`并接收`BlockedListEvent`，此时它可以通知适当的各方。

Spring 的事件机制是为同一应用程序上下文中的 Spring bean 之间的简单通信而设计的。然而，对于更复杂的企业集成需求，单独维护的 [Spring Integration](https://projects.spring.io/spring-integration/)项目为 构建基于众所周知的 Spring 编程模型的 轻量级、[面向模式、事件驱动的架构提供了完整的支持。](https://www.enterpriseintegrationpatterns.com/)

**基于注解的事件监听器**

`@EventListener`您可以使用注解在托管 bean 的任何方法上注册事件侦听器 。`BlockedListNotifier`可以改写如下：

```java
public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}
```

方法签名再次声明了它所侦听的事件类型，但是这一次使用了一个灵活的名称并且没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析您的泛型参数，也可以通过泛型来缩小事件类型。

如果您的方法应该监听多个事件，或者如果您想在没有参数的情况下定义它，也可以在注解本身上指定事件类型。以下示例显示了如何执行此操作：

```java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
```

还可以通过使用定义[`SpEL`表达式](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions)的注解`condition`属性添加额外的运行时过滤，表达式应该匹配以实际调用特定事件的方法。

以下示例显示了如何重写我们的通知器以仅在事件的属性`content`等于`my-event`时才被调用 ：

```java
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}
```

每个`SpEL`表达式都针对专用上下文进行评估。下表列出了对上下文可用的项目，以便您可以将它们用于条件事件处理：

| 名称     | 位置    | 描述                                                                                            | 例子                                                |
| ------ | ----- | --------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| 事件     | 根对象   | 实际的`ApplicationEvent`.                                                                        | `#root.event`或者`event`                            |
| 参数数组   | 根对象   | 用于调用方法的参数（作为对象数组）。                                                                            | `#root.args`或`args`；`args[0]`访问第一个参数等。            |
| *参数名称* | 评估上下文 | 任何方法参数的名称。如果由于某种原因，名称不可用（例如，因为编译的字节码中没有调试信息），也可以使用代表参数索引的`#a<#arg>`语法`<#arg>`（从 0 开始）使用单独的参数。 | `#blEvent`或`#a0`（您也可以使用`#p0`或`#p<#arg>`参数表示法作为别名） |

请注意，`#root.event`使您可以访问底层事件，即使您的方法签名实际上是指已发布的任意对象。

如果您需要发布一个事件作为处理另一个事件的结果，您可以更改方法签名以返回应该发布的事件，如以下示例所示：

```java
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
```

[异步侦听](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-functionality-events-async) 器不支持此功能 。

`handleBlockedListEvent()`方法为它处理的每一个`ListUpdateEvent`发布一个新的`BlockedListEvent`。如果您需要发布多个事件，则可以改为返回一个`Collection`或一组事件。

**异步侦听器**

如果您希望特定侦听器异步处理事件，则可以重用 [常规`@Async`支持](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-async)。以下示例显示了如何执行此操作：

```java
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}
```

使用异步事件时请注意以下限制：

* 如果异步事件侦听器抛出`Exception`，它不会传播给调用者。有关 [`AsyncUncaughtExceptionHandler`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/aop/interceptor/AsyncUncaughtExceptionHandler.html) 更多详细信息，请参阅。
* 异步事件侦听器方法不能通过返回值来发布后续事件。如果您需要发布另一个事件作为处理的结果，请 [`ApplicationEventPublisher`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/ApplicationEventPublisher.html) 手动注入一个来发布该事件。

**订购听众**

如果您需要在另一个侦听器之前调用一个侦听器，您可以`@Order` 在方法声明中添加注解，如以下示例所示：

```java
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}
```

**通用事件**

您还可以使用泛型来进一步定义事件的结构。考虑使用 `EntityCreatedEvent<T>`，`T`是创建的实际实体的类型。例如，您可以创建以下侦听器定义以仅接收`Person`类型的`EntityCreatedEvent`：

```java
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
```

由于类型擦除，这仅在触发的事件解析了事件侦听器过滤的通用参数（即类似的东西 `class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }`）时才有效。

在某些情况下，如果所有事件都遵循相同的结构，这可能会变得非常乏味（就像前面示例中的事件一样）。在这种情况下，您可以实施`ResolvableTypeProvider`以引导框架超出运行时环境提供的范围。以下事件显示了如何执行此操作：

```java
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}
```

这不仅适用于 ApplicationEvent，还适用于作为事件发送的任何任意对象。

**1.15.3. 方便访问底层资源**

为了优化使用和理解应用程序上下文，您应该熟悉 Spring 的`Resource`抽象，如[参考资料](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources)中所述。

应用程序上下文是`ResourceLoader`，可用于加载`Resource`对象。`Resource`本质上是 JDK`java.net.URL`类的功能更丰富的版本。事实上，在适当的地方包装一个`Resource`实例的实现。`java.net.URL`A`Resource`可以以透明的方式从几乎任何位置获取低级资源，包括从类路径、文件系统位置、可使用标准 URL 描述的任何位置以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径，则这些资源的来源是特定的并且适合于实际的应用程序上下文类型。

您可以配置部署到应用程序上下文中的 bean 来实现特殊的回调接口，`ResourceLoaderAware`在初始化时自动回调，应用程序上下文本身作为`ResourceLoader`. 您还可以公开 type 的属性，`Resource`用于访问静态资源。它们像任何其他属性一样被注入其中。您可以将这些`Resource` 属性指定为简单路径，并在部署 bean 时`String`依赖从这些文本字符串到实际对象的自动转换。`Resource`

提供给构造函数的一个或多个位置路径`ApplicationContext`实际上是资源字符串，并且以简单的形式，根据特定的上下文实现进行适当的处理。例如`ClassPathXmlApplicationContext`，将简单的位置路径视为类路径位置。您还可以使用带有特殊前缀的位置路径（资源字符串）来强制从类路径或 URL 加载定义，而不管实际的上下文类型如何。

**1.15.4. 应用程序启动跟踪**

`ApplicationContext`管理 Spring 应用程序的生命周期并围绕组件提供丰富的编程模型。因此，复杂的应用程序可以具有同样复杂的组件图和启动阶段。

使用特定指标跟踪应用程序启动步骤可以帮助了解在启动阶段花费的时间，但它也可以用作更好地了解整个上下文生命周期的一种方式。

（`AbstractApplicationContext`及其子类）使用 `ApplicationStartup`进行检测 ，它收集`StartupStep`有关各种启动阶段的数据：

* 应用程序上下文生命周期（基础包扫描、配置类管理）
* bean 生命周期（实例化、智能初始化、后处理）
* 应用事件处理

以下是`AnnotationConfigApplicationContext`仪器仪表的示例：

```java
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
```

应用程序上下文已经配备了多个步骤。记录后，可以使用特定工具收集、显示和分析这些启动步骤。有关现有启动步骤的完整列表，您可以查看 [专用的附录部分](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#application-startup-steps)。

默认`ApplicationStartup`实现是无操作变体，以最小化开销。这意味着默认情况下在应用程序启动期间不会收集任何指标。Spring Framework 附带了一个使用 Java Flight Recorder 跟踪启动步骤的实现： `FlightRecorderApplicationStartup`. 要使用此变体，您必须在创建它后立即配置它的实例`ApplicationContext`。

如果开发人员提供自己的 `AbstractApplicationContext` 子类，或者希望收集更精确的数据，那么他们还可以使用 `ApplicationStartup` 基础设施

`ApplicationStartup`仅在应用程序启动期间和核心容器中使用；这绝不是 Java 分析器或[Micrometer](https://micrometer.io/)等指标库的替代品。

要开始收集自定义 `StartupStep`，组件可以 直接从应用程序上下文中获取`ApplicationStartup`实例，使它们的组件实现`ApplicationStartupAware`，或者在任何注入点请求`ApplicationStartup`类型。

开发人员在创建自定义启动步骤时 不应使用`"spring.*"`命名空间。这个命名空间是为内部 Spring 使用而保留的，并且可能会发生变化。

**1.15.5. 方便的 Web 应用程序 ApplicationContext 实例化**

您可以使用例如`ApplicationContext`以声明方式创建实例 `ContextLoader`。当然，您也可以使用其中一种`ApplicationContext`实现以编程方式创建`ApplicationContext`实例。

您可以使用`ContextLoaderListener`注册一个`ApplicationContext`，如以下示例所示：

```xml
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
```

侦听器检查`contextConfigLocation`参数。如果该参数不存在，则侦听器`/WEB-INF/applicationContext.xml`用作默认值。当参数确实存在时，侦听`String`器使用预定义的分隔符（逗号、分号和空格）分隔 ，并将这些值用作搜索应用程序上下文的位置。也支持 Ant 样式的路径模式。示例是`/WEB-INF/*Context.xml`（对于名称以 `Context.xml`结尾 且驻留在`WEB-INF`目录中的所有文件）和`/WEB-INF/**/*Context.xml` （对于`WEB-INF` 的任何子目录中的所有此类文件）。

**1.15.6. 将 Spring 部署`ApplicationContext`为 Java EE RAR 文件**

可以将 Spring 部署`ApplicationContext`为 RAR 文件，将上下文及其所有必需的 bean 类和库 JAR 封装在 Java EE RAR 部署单元中。`ApplicationContext`这相当于引导一个能够访问 Java EE 服务器设施的独立设备（仅托管在 Java EE 环境中）。RAR 部署是部署无头 WAR 文件的一种更自然的替代方案——实际上，一个没有任何 HTTP 入口点的 WAR 文件，仅用于 `ApplicationContext`在 Java EE 环境中引导 Spring。

RAR 部署非常适合不需要 HTTP 入口点而是仅包含消息端点和计划作业的应用程序上下文。这种上下文中的 Bean 可以使用应用程序服务器资源，例如 JTA 事务管理器和 JNDI 绑定的 JDBC `DataSource`实例和 JMS`ConnectionFactory`实例，还可以向平台的 JMX 服务器注册——所有这些都通过 Spring 的标准事务管理和 JNDI 和 JMX 支持工具。应用程序组件还可以`WorkManager`通过 Spring 的`TaskExecutor`抽象与应用程序服务器的 JCA 交互。

[`SpringContextResourceAdapter`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/jca/context/SpringContextResourceAdapter.html) 有关RAR 部署中涉及的配置详细信息，请参阅该类的 javadoc 。

对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件：

1. 将所有应用程序类打包成一个 RAR 文件（这是一个具有不同文件扩展名的标准 JAR 文件）。
2. 将所有必需的库 JAR 添加到 RAR 存档的根目录中。
3. 添加 `META-INF/ra.xml`部署描述符（如[javadoc 中`SpringContextResourceAdapter`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/jca/context/SpringContextResourceAdapter.html)所示）和相应的 Spring XML bean 定义文件（通常 `META-INF/applicationContext.xml`）。
4. 将生成的 RAR 文件拖放到应用程序服务器的部署目录中。

这种 RAR 部署单元通常是独立的。它们不会将组件暴露给外部世界，甚至不会暴露给同一应用程序的其他模块。与基于 RAR 的交互`ApplicationContext`通常通过它与其他模块共享的 JMS 目标发生。例如，基于 RAR 的程序`ApplicationContext`还可以安排一些作业或对文件系统中的新文件（或类似文件）做出反应。如果它需要允许来自外部的同步访问，它可以（例如）导出 RMI 端点，这些端点可以被同一台机器上的其他应用程序模块使用。

#### 1.16. `BeanFactory`API

API 为 Spring的`BeanFactory`IoC 功能提供了底层基础。它的具体契约多用于与 Spring 的其他部分和相关的第三方框架的集成，它的`DefaultListableBeanFactory`实现是上层`GenericApplicationContext`容器内的关键委托。

和`BeanFactory`相关的接口（例如`BeanFactoryAware`、`InitializingBean`、 `DisposableBean`）是其他框架组件的重要集成点。通过不需要任何注解甚至反射，它们允许容器与其组件之间非常有效的交互。应用程序级别的 bean 可以使用相同的回调接口，但通常更喜欢声明性依赖注入，或者通过注解或通过编程配置。

请注意，核心`BeanFactory`API 级别及其`DefaultListableBeanFactory` 实现不会对要使用的配置格式或任何组件注解做出假设。所有这些风格都通过扩展（例如`XmlBeanDefinitionReader`和`AutowiredAnnotationBeanPostProcessor`）出现，并将共享`BeanDefinition`对象作为核心元数据表示进行操作。这就是使 Spring 的容器如此灵活和可扩展的本质。

**1.16.1.`BeanFactory`还是`ApplicationContext`？**

`BeanFactory`本节解释了容器级别和 容器级别之间的差异`ApplicationContext`以及对引导的影响。

`ApplicationContext`除非您有充分的理由不这样做，否则 您应该使用 an`GenericApplicationContext`及其子类`AnnotationConfigApplicationContext` 作为自定义引导的常见实现。这些是 Spring 核心容器的主要入口点，用于所有常见目的：加载配置文件、触发类路径扫描、以编程方式注册 bean 定义和带注解的类，以及（从 5.0 开始）注册功能 bean 定义。

因为 an`ApplicationContext`包含 a 的所有功能`BeanFactory`，所以通常建议在 plain 上使用`BeanFactory`，除了需要完全控制 bean 处理的场景。在一个`ApplicationContext`（例如 `GenericApplicationContext`实现）中，按照约定（即按 bean 名称或按 bean 类型——特别是后处理器）检测几种 bean，而 plain`DefaultListableBeanFactory`对任何特殊 bean 是不可知的。

对于许多扩展容器特性，例如注解处理和 AOP 代理，[`BeanPostProcessor`扩展点](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-bpp)是必不可少的。如果您仅使用普通`DefaultListableBeanFactory`的，则默认情况下不会检测和激活此类后处理器。这种情况可能会令人困惑，因为您的 bean 配置实际上没有任何问题。相反，在这种情况下，需要通过额外的设置来完全引导容器。

下表列出了`BeanFactory`和 `ApplicationContext`接口和实现提供的功能。

| 特征                             | `BeanFactory` | `ApplicationContext` |
| ------------------------------ | ------------- | -------------------- |
| Bean实例化/织入                     | 是的            | 是的                   |
| 集成的生命周期管理                      | 不             | 是的                   |
| 自动`BeanPostProcessor`注册        | 不             | 是的                   |
| 自动`BeanFactoryPostProcessor`注册 | 不             | 是的                   |
| 方便`MessageSource`的访问（国际化）      | 不             | 是的                   |
| 内置`ApplicationEvent`发布机制       | 不             | 是的                   |

要使用`DefaultListableBeanFactory` 显式注册 bean 后处理器，您需要以编程方式调用`addBeanPostProcessor`，如以下示例所示：

```java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory
```

要将 `BeanFactoryPostProcessor`应用于`DefaultListableBeanFactory`，您需要调用其`postProcessBeanFactory`方法，如以下示例所示：

```java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);
```

在这两种情况下，显式注册步骤都很不方便，这就是为什么在 Spring 支持的应用程序中，各种`ApplicationContext`变体比普通的更受青睐 ，尤其是在典型企业设置中依赖实例来扩展容器功能时`DefaultListableBeanFactory`。`BeanFactoryPostProcessor``BeanPostProcessor`

An`AnnotationConfigApplicationContext`已注册所有常见的注解后处理器，并且可以通过配置注解引入额外的处理器，例如`@EnableTransactionManagement`. 在 Spring 的基于注解的配置模型的抽象级别上，bean 后处理器的概念变成了单纯的内部容器细节。


# 1.2. 容器概述

`org.springframework.context.ApplicationContext`接口代表 Spring IoC 容器，负责实例化、配置和组装 bean。容器通过读取配置元数据来获取关于要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

Spring 提供了`ApplicationContext`接口的几个实现。在独立应用程序中，通常会创建 [`ClassPathXmlApplicationContext`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/support/ClassPathXmlApplicationContext.html) 或[`FileSystemXmlApplicationContext`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/context/support/FileSystemXmlApplicationContext.html)的实例。虽然 XML 一直是定义配置元数据的传统格式，但您可以通过提供少量 XML 配置来以声明方式支持这些附加元数据格式，从而指示容器使用 Java 注解或代码作为元数据格式。

在大多数应用场景中，不需要显式用户代码来实例化 Spring IoC 容器的一个或多个实例。例如，在 Web 应用程序场景中，应用程序`web.xml`文件中简单的八（大约）行样板 Web 描述符 XML 通常就足够了（请参阅[Web 应用程序的便捷 ApplicationContext 实例化](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#context-create)）。如果您使用 [Spring Tools for Eclipse](https://spring.io/tools)（一个 Eclipse 驱动的开发环境），您可以通过几次鼠标单击或击键轻松创建此样板配置。

下图显示了 Spring 如何工作的高级视图。您的应用程序类与配置元数据相结合，以便在`ApplicationContext`创建和初始化之后，您拥有一个完全配置且可执行的系统或应用程序。

![容器魔法](https://docs.spring.io/spring-framework/reference/_images/container-magic.png)

图 1. Spring IoC 容器

**1.2.1. 配置元数据**

如上图所示，Spring IoC 容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化、配置和组装应用程序中的对象。

配置元数据传统上以简单直观的 XML 格式提供，本章大部分内容都使用这种格式来传达 Spring IoC 容器的关键概念和特性。

基于 XML 的元数据不是唯一允许的配置元数据形式。Spring IoC 容器本身与实际编写此配置元数据的格式完全分离。如今，许多开发人员 为其 Spring 应用程序 选择[基于 Java 的配置。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java)

有关在 Spring 容器中使用其他形式的元数据的信息，请参阅：

* [基于注解的配置](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-annotation-config)：Spring 2.5 引入了对基于注解的配置元数据的支持。
* [基于 Java 的配置](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java)：从 Spring 3.0 开始，Spring JavaConfig 项目提供的许多特性成为核心 Spring Framework 的一部分。因此，您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能，请参阅 [`@Configuration`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html)、 [`@Bean`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html)、 [`@Import`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Import.html)和[`@DependsOn`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/DependsOn.html)注解。

Spring 配置包含容器必须管理的至少一个，通常是多个 bean 定义。基于 XML 的配置元数据将这些 bean 配置为`<bean/>`顶级元素内的`<beans/>`元素。Java 配置通常在`@Configuration`类中使用`@Bean` 注解的方法。

这些 bean 定义对应于构成应用程序的实际对象。通常，您定义服务层对象、数据访问对象 (DAO)、表示对象（如 Struts`Action`实例）、基础设施对象（如 Hibernate `SessionFactories`、JMS`Queues`等）。通常，不会在容器中配置细粒度的域对象，因为创建和加载域对象通常是 DAO 和业务逻辑的责任。但是，您可以使用 Spring 与 AspectJ 的集成来配置在 IoC 容器控制之外创建的对象。请参阅[Using AspectJ to dependency-inject domain objects with Spring](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-atconfigurable)。

以下示例显示了基于 XML 的配置元数据的基本结构：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>
```

`id`属性是标识单个 bean 定义的字符串。

`class`属性定义 bean 的类型并使用完全限定的类名。

`id`属性的值是指协作对象。此示例中未显示用于引用协作对象的 XML。有关更多信息，请参阅 [依赖项。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies)

**1.2.2. 实例化一个容器**

提供给`ApplicationContext`构造函数的一个或多个位置路径是资源字符串，允许容器从各种外部资源（例如本地文件系统、Java `CLASSPATH`等）加载配置元数据。

```java
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
```

在了解了 Spring 的 IoC 容器之后，您可能想了解更多关于 Spring 的 `Resource`抽象（如[参考资料](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources)中所述），它提供了一种方便的机制来从 URI 语法中定义的位置读取 InputStream。特别是， `Resource`路径用于构造应用程序上下文，如[应用程序上下文和资源路径](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-app-ctx)中所述。

以下示例显示了服务层对象`(services.xml)`配置文件：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>
```

以下示例显示了数据访问对象`daos.xml`文件：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>
```

在前面的示例中，服务层由`PetStoreServiceImpl`类和两个类型的数据访问对象`JpaAccountDao`和`JpaItemDao`（基于 JPA 对象-关系映射标准）组成。`property name`元素引用JavaBean 属性的名称，元素`ref`引用另一个bean 定义的名称。元素`id`之间的这种`ref`联系表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息，请参阅 [依赖](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies)项。

**编写基于 XML 的配置元数据**

让 bean 定义跨越多个 XML 文件会很有用。通常，每个单独的 XML 配置文件都代表架构中的一个逻辑层或模块。

您可以使用应用程序上下文构造函数从所有这些 XML 片段加载 bean 定义。此构造函数采用多个`Resource`位置，如上 [一节](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-instantiation)所示。或者，使用`<import/>`元素的一个或多个实例从另一个文件或多个文件加载 bean 定义。以下示例显示了如何执行此操作：

```xml
<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>
```

在前面的示例中，外部 bean 定义是从三个文件加载的： `services.xml`、`messageSource.xml`和`themeSource.xml`. 所有位置路径都相对于执行导入的定义文件，因此`services.xml`必须与执行导入的文件位于同一目录或类路径位置，而 `messageSource.xml`和`themeSource.xml`必须位于导入文件位置下方的`resources`位置。如您所见，前导斜杠被忽略。但是，鉴于这些路径是相对的，最好不要使用斜线。根据 Spring Schema ，被导入文件的内容，包括顶级`<beans/>`元素，必须是有效的 XML bean 定义。

> 可以但不推荐使用相对“../”路径来引用父目录中的文件。这样做会创建对当前应用程序之外的文件的依赖。特别是，不建议将此引用用于`classpath:`URL（例如，`classpath:../services.xml`），其中运行时解析过程选择“最近的”类路径根，然后查看其父目录。类路径配置更改可能会导致选择不同的、不正确的目录。您始终可以使用完全限定的资源位置而不是相对路径：例如，`file:C:/config/services.xml`或`classpath:/config/services.xml`. 但是，请注意您将应用程序的配置耦合到特定的绝对位置。通常最好为此类绝对位置保留间接性——例如，通过在运行时针对 JVM 系统属性解析的“${...}”占位符。

命名空间本身提供了导入指令功能。除了普通的 bean 定义之外，更多的配置特性可以在 Spring 提供的 XML 命名空间的选择中使用——例如，`context`和`util`命名空间。

**Groovy Bean 定义 DSL**

作为外部化配置元数据的另一个示例，bean 定义也可以在 Spring 的 Groovy Bean 定义 DSL 中表示，如 Grails 框架中已知的那样。通常，此类配置位于“.groovy”文件中，其结构如下例所示：

```groovy
beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}
```

这种配置风格在很大程度上等同于 XML bean 定义，甚至支持 Spring 的 XML 配置命名空间。它还允许通过`importBeans`指令导入 XML bean 定义文件。

**1.2.3. 使用容器**

`ApplicationContext`是一个高级工厂的接口，能够维护不同 bean 及其依赖项的注册表。通过使用方法`T getBean(String name, Class<T> requiredType)`，您可以检索 bean 的实例。

`ApplicationContext`允许您读取 bean 定义并访问它们，如以下示例所示：

```java
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();
```

使用 Groovy 配置，引导看起来非常相似。它有一个不同的上下文实现类，它支持 Groovy（但也理解 XML bean 定义）。以下示例显示了 Groovy 配置：

```java
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
```

最灵活的变体是`GenericApplicationContext`与阅读器委托结合使用——例如，`XmlBeanDefinitionReader`与XML 文件结合使用，如以下示例所示：

```java
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
```

您还可以对Groovy 文件使用`GroovyBeanDefinitionReader`，如以下示例所示：

```java
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
```

您可以在同一个`ApplicationContext`上混合和匹配此类读取器委托，从不同的配置源读取 bean 定义。

然后，您可以使用`getBean`来检索 bean 的实例。`ApplicationContext` 接口还有一些其他检索 bean 的方法，但理想情况下，您的应用程序代码不应该使用它们。实际上，您的应用程序代码根本不应该调用 `getBean()`方法，因此根本不依赖 Spring API。例如，Spring 与 Web 框架的集成为各种 Web 框架组件（例如控制器和 JSF 管理的 bean）提供了依赖注入，让您可以通过元数据（例如自动装配注解）声明对特定 bean 的依赖。


# 1.3. Bean概述

Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的（例如，XML以 `<bean/>`定义的形式）。

在容器本身中，这些 bean 定义表示为`BeanDefinition` 对象，其中包含（以及其他信息）以下元数据：

* 一个包限定的类名：通常是被定义的 bean 的实际实现类。
* Bean 行为配置元素，它说明 bean 在容器中的行为方式（范围、生命周期回调等）。
* 对 bean 完成工作所需的其他 bean 的引用。这些引用也称为协作者或依赖项。
* 要在新创建的对象中设置的其他配置设置——例如，池的大小限制或在管理连接池的 bean 中使用的连接数。

此元数据转换为组成每个 bean 定义的一组属性。下表描述了这些属性：

| Property                 | 解释...                                                                                                                           |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Class                    | [实例化 Bean](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-class)                   |
| Name                     | [命名 Bean](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-beanname)                         |
| Scope                    | [Bean范围](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes)                    |
| Constructor arguments    | [依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-collaborators)               |
| Properties               | [依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-collaborators)               |
| Autowiring mode          | [自动装配协作者](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire)                 |
| Lazy initialization mode | [延迟初始化的 Bean](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lazy-init)            |
| Initialization method    | [初始化回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean) |
| Destruction method       | [销毁回调](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-disposablebean)    |

除了包含有关如何创建特定 bean 的信息的 bean 定义之外，`ApplicationContext`的实现还允许注册在容器外部（由用户）创建的现有对象。这是通过 `getBeanFactory()` 方法访问 ApplicationContext 的 `BeanFactory` 来完成的，该方法返回 `DefaultListableBeanFactory` 实现。`DefaultListableBeanFactory` 通过 `registerSingleton(..)` 和 `registerBeanDefinition(..)` 方法支持这种注册。然而，典型的应用程序仅使用通过常规 bean 定义元数据定义的 bean。

> 需要尽早注册 Bean 元数据和手动提供的单例实例，以便容器在自动装配和其他自省步骤中正确推理它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例，但官方不支持在运行时注册新 bean（同时对工厂进行实时访问），并可能导致并发访问异常、bean 容器中的状态不一致或两个都。

**1.3.1. Bean命名**

每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。但是，如果它需要多个，则可以将多余的视为别名。

在基于 XML 的配置元数据中，您可以使用`id`属性、`name`属性或两者来指定 bean 标识符。该`id`属性可让您准确指定一个 id。按照惯例，这些名称是字母数字的（“myBean”、“someService”等），但它们也可以包含特殊字符。如果要为 bean 引入其他别名，也可以在`name` 属性中指定，用逗号 ( `,`)、分号 ( `;`) 或空格分隔。作为历史记录，在 Spring 3.1 之前的版本中，`id`属性被定义为一种`xsd:ID`类型，它限制了可能的字符。从 3.1 开始，它被定义为一种`xsd:string`类型。请注意，`id`容器仍然强制执行 bean 唯一性，但不再由 XML 解析器强制执行。

您不需要为 bean 提供`name`或 `id`。如果您不显式提供 `name`或`id`，则容器会为该 bean 生成一个唯一名称。但是，如果您想通过名称引用该 bean，通过使用`ref`元素或服务定位器样式查找，您必须提供名称。不提供名称的动机与使用[内部 bean](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-inner-beans)和[自动装配合作者](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire)有关。

Bean 命名约定

约定是在命名 bean 时对实例字段名称使用标准 Java 约定。也就是说，bean 名称以小写字母开头，并且从那里开始是驼峰式的。此类名称的示例包括`accountManager`、 `accountService`、`userDao`、`loginController`等。

命名 bean 的一致性使您的配置更易于阅读和理解。此外，如果您使用 Spring AOP，则在将建议应用于一组按名称相关的 bean 时会很有帮助。

通过类路径中的组件扫描，Spring 为未命名的组件生成 bean 名称，遵循前面描述的规则：本质上，采用简单的类名称并将其初始字符转换为小写。但是，在（不寻常的）特殊情况下，当有多个字符并且第一个和第二个字符都是大写时，会保留原始大小写。这些与`java.beans.Introspector.decapitalize`（Spring 在此处使用的） 定义的规则相同。

**在 Bean 定义之外为 Bean 起别名**

在 bean 定义本身中，您可以为 bean 提供多个名称，方法是使用属性指定的最多一个名称`id`和属性`name`中任意数量的其他名称的组合。这些名称可以是同一个 bean 的等效别名，并且在某些情况下很有用，例如让应用程序中的每个组件通过使用特定于该组件本身的 bean 名称来引用公共依赖项。

但是，指定实际定义 bean 的所有别名并不总是足够的。有时需要为在别处定义的 bean 引入别名。这在大型系统中很常见，其中配置在每个子系统之间进行拆分，每个子系统都有自己的一组对象定义。在基于 XML 的配置元数据中，您可以使用`<alias/>`元素来完成此操作。以下示例显示了如何执行此操作：

```xml
<alias name="fromName" alias="toName"/>
```

在这种情况下，命名`fromName`的 bean（在同一容器中）也可以在使用此别名定义后称为`toName`.

例如，子系统 A 的配置元数据可能引用名为`subsystemA-dataSource` 的 DataSource 。子系统 B 的配置元数据可能会引用名称为 `subsystemB-dataSource`的 DataSource。在编写同时使用这两个子系统的主应用程序时，主应用程序通过名称 `myApp-dataSource`来引用 DataSource。要让所有三个名称都引用同一个对象，您可以将以下别名定义添加到配置元数据中：

```xml
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
```

现在每个组件和主应用程序都可以通过一个唯一的名称来引用数据源，并保证不会与任何其他定义冲突（有效地创建一个命名空间），但它们引用的是同一个 bean。

Java 配置

如果您使用 Java配置，则可以使用注解`@Bean`来提供别名。有关详细信息，请参阅[使用`@Bean`注解](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-bean-annotation)。

**1.3.2. 实例化 Bean**

bean 定义本质上是创建一个或多个对象的方法。当被访问时，容器会查看命名 bean 的定义，并使用该 bean 定义封装的配置元数据来创建（或获取）实际对象。

如果您使用基于 XML 的配置元数据，您可以在元素`<bean/>`的`class`属性中指定要实例化的对象的类型（或类） 。 `class`属性（在内部是实例`BeanDefinition`的`Class`属性 ）通常是必需的。（有关例外情况，请参阅 [使用实例工厂方法](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-class-instance-factory-method)和[Bean 定义继承](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-child-bean-definitions)进行实例化。）您可以通过以下两种方式之一使用`Class`属性：

* 通常，在容器本身通过反射调用其构造函数直接创建 bean 的情况下，指定要构造的 bean 类，有点等价于 Java 代码中的`new`操作符。
* 指定包含被调用以创建对象的`static`工厂方法的实际类，在不太常见的情况下，容器调用类上的`static`工厂方法来创建 bean。调用`static`工厂方法返回的对象类型可能是同一个类，也可能完全是另一个类。

嵌套类名

如果要为嵌套类配置 bean 定义，可以使用嵌套类的二进制名称或源名称。

例如，如果您在`com.example`包中调用了一个`SomeThing`类，并且`SomeThing`类有一个`static`名为`OtherThing` 的嵌套类，则它们可以用美元符号 ( `$`) 或点 ( `.`) 分隔。所以bean 定义中属性`class`的值是`com.example.SomeThing$OtherThing`或 `com.example.SomeThing.OtherThing`。

**使用构造函数进行实例化**

当您通过构造方法创建 bean 时，所有普通类都可以被 Spring 使用并兼容。也就是说，正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 bean 类就足够了。但是，根据您用于该特定 bean 的 IoC 类型，您可能需要一个默认（空）构造函数。

Spring IoC 容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBean。大多数 Spring 用户更喜欢只有一个默认（无参数）构造函数以及根据容器中的属性建模的适当的 setter 和 getter 的实际 JavaBeans。您还可以在容器中拥有更多奇特的非 bean 样式类。例如，如果您需要使用绝对不符合 JavaBean 规范的遗留连接池，那么 Spring 也可以管理它。

使用基于 XML 的配置元数据，您可以指定 bean 类，如下所示：

```xml
<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
```

有关在构造对象后向构造函数提供参数（如果需要）和设置对象实例属性的机制的详细信息，请参阅 [注入依赖项](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-collaborators)。

**使用静态工厂方法进行实例化**

在定义使用静态工厂方法创建的 bean 时，使用`class` 属性指定包含`static`工厂方法的类，使用命名属性`factory-method`指定工厂方法本身的名称。您应该能够调用此方法（使用可选参数，如后所述）并返回一个活动对象，该对象随后被视为是通过构造函数创建的。这种 bean 定义的一种用途是在遗留代码中调用`static`工厂。

以下 bean 定义指定将通过调用工厂方法创建 bean。定义没有指定返回对象的类型（类），而是包含工厂方法的类。在此示例中， `createInstance()`方法必须是`static`方法。以下示例显示了如何指定工厂方法：

```xml
<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
```

下面的例子展示了一个可以与前面的 bean 定义一起工作的类：

```java
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
```

有关从工厂返回对象后向工厂方法提供（可选）参数和设置对象实例属性的机制的详细信息，请参阅[依赖关系和配置详述](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-properties-detailed)。

**使用实例工厂方法进行实例化**

与通过[静态工厂方法](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-class-static-factory-method)进行实例化类似，使用实例工厂方法进行实例化会从容器中调用现有 bean 的非静态方法来创建新 bean。要使用此机制，请将`class`属性留空，并在`factory-bean`属性中指定当前（或父级或祖先）容器中的 bean 的名称，该容器包含要调用以创建对象的实例方法。使用属性`factory-method`设置工厂方法本身的名称。以下示例显示了如何配置这样的 bean：

```xml
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
```

以下示例显示了相应的类：

```java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}
```

一个工厂类也可以包含多个工厂方法，如下例所示：

```xml
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
```

以下示例显示了相应的类：

```java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}
```

这种方法表明，工厂 bean 本身可以通过依赖注入 (DI) 进行管理和配置。请参阅[依赖项和配置详细信息](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-properties-detailed)。

在 Spring 文档中，“工厂 bean”是指在 Spring 容器中配置并通过 [实例](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-class-instance-factory-method)或 [静态](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-class-static-factory-method)工厂方法创建对象的 bean。相比之下， `FactoryBean`（注意大写）指的是特定于 Spring 的 [`FactoryBean`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-factorybean)实现类。

**确定 Bean 的运行时类型**

确定特定 bean 的运行时类型并非易事。bean 元数据定义中的指定类只是一个初始类引用，可能与声明的工厂方法结合，或者是可能导致 bean 的不同运行时类型的`FactoryBean`类，或者在实例的情况下根本没有设置实例级别工厂方法（而是通过指定的`factory-bean`名称解析）。此外，AOP 代理可以用基于接口的代理包装一个 bean 实例，并限制目标 bean 的实际类型（仅其实现的接口）的暴露。

查找特定 bean 的实际运行时类型的推荐方法是`BeanFactory.getType`调用指定的 bean 名称。这会考虑上述所有情况， `BeanFactory.getBean` 将为相同 bean 名称返回的对象类型。


# 1.4. 依赖项

典型的企业应用程序不包含单个对象（或 Spring 术语中的 bean）。即使是最简单的应用程序也有一些对象协同工作，以呈现最终用户认为的连贯应用程序。下一节将解释如何从定义多个独立的 bean 定义到完全实现的应用程序，其中对象协作以实现目标。


# 1.4.1. 依赖注入

依赖注入 (DI) 是一个过程，对象仅通过构造函数参数、工厂方法的参数或对象实例在构造或从工厂方法返回。然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身通过使用直接构造类或服务定位器模式来控制其依赖项的实例化或位置的逆过程（因此称为控制反转）。

使用 DI 原则，代码更干净，当对象具有依赖关系时，解耦更有效。对象不查找其依赖项，也不知道依赖项的位置或类别。结果，您的类变得更容易测试，特别是当依赖关系在接口或抽象基类上时，它们允许在单元测试中使用存根或模拟实现。

DI 存在两个主要变体：[基于构造函数的依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-injection)和[基于 Setter 的依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-setter-injection)。

**基于构造函数的依赖注入**

基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的，每个参数代表一个依赖项。调用带有特定参数的`static`工厂方法来构造 bean 几乎是等价的，本次讨论将类似地对待构造函数和`static`工厂方法的参数。以下示例显示了一个只能通过构造函数注入进行依赖注入的类：

```java
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
```

请注意，这个类没有什么特别之处。它是一个 POJO，不依赖于容器特定的接口、基类或注解。

**构造函数参数解析**

构造函数参数解析匹配通过使用参数的类型进行。如果 bean 定义的构造函数参数中不存在潜在的歧义，则在 bean 定义中定义构造函数参数的顺序是在实例化 bean 时将这些参数提供给适当构造函数的顺序。考虑以下类：

```java
package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
```

假设`ThingTwo`和`ThingThree`类不通过继承相关，则不存在潜在的歧义。因此，以下配置工作正常，您无需在 `<constructor-arg/>`元素中显式指定构造函数参数索引或类型。

```xml
<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>
```

当引用另一个 bean 时，类型是已知的，并且可以发生匹配（就像前面的示例一样）。当使用简单类型时，如 `<value>true</value>`，Spring 无法确定值的类型，因此无法在没有帮助的情况下按类型匹配。例如以下类：

```java
package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
```

*构造函数参数类型匹配*

在上述场景中，如果您通过属性`type`显式指定构造函数参数的类型，则容器可以使用简单类型的类型匹配，如以下示例所示：

```xml
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
```

*构造函数参数下标*

您可以使用`index`属性显式指定构造函数参数的下标，如以下示例所示：

```xml
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>
```

除了解决多个简单值的歧义之外，指定索引还可以解决构造函数具有两个相同类型参数的歧义。

下标从 0 开始。

*构造函数参数名称*

您还可以使用构造函数参数名称进行值消歧，如以下示例所示：

```xml
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>
```

请记住，要使这项工作开箱即用，您的代码必须在启用调试标志的情况下编译，以便 Spring 可以从构造函数中查找参数名称。如果您不能或不想使用调试标志编译代码，则可以使用 JDK 注解[@ConstructorProperties](https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.html) 显式命名构造函数参数。示例类必须如下所示：

```java
package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
```

**基于 Setter 的依赖注入**

基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数`static`工厂方法来实例化 bean 后调用 bean 上的 setter 方法来完成的。

以下示例显示了一个只能通过使用纯 setter 注入进行依赖注入的类。这个类是传统的Java。它是一个 POJO，不依赖于容器特定的接口、基类或注解。

```java
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
```

`ApplicationContext`管理的bean 支持基于构造函数和基于 setter 的 DI。在已经通过构造方法注入了一些依赖项之后，它还支持基于 setter 的 DI。您以 `BeanDefinition` 的形式配置依赖项，您可以将其与`PropertyEditor`实例结合使用以将属性从一种格式转换为另一种格式。然而，大多数 Spring 用户并不直接使用这些类（即以编程方式），而是使用 XML`bean` 定义、带注解的组件（即，使用`@Component`、 `@Controller`等注解的类）或基于 Java 的`@Configuration`类中的`@Bean`方法。然后这些源在内部转换为实例`BeanDefinition`并用于加载整个 Spring IoC 容器实例。

**基于构造函数还是基于 setter 的 DI？**

由于您可以混合使用基于构造函数和基于 setter 的 DI，因此将构造函数用于强制依赖项并将 setter 方法或配置方法用于可选依赖项是一个很好的经验法则。请注意，在 setter 方法上使用[@Required](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-required-annotation) 注解可用于使属性成为必需的依赖项；然而，带有参数的编程验证的构造函数注入是更可取的。

Spring 团队通常提倡构造函数注入，因为它允许您将应用程序组件实现为不可变对象，并确保所需的依赖项不是`null`. 此外，构造函数注入的组件总是以完全初始化的状态返回给客户端（调用）代码。附带说明一下，大量的构造函数参数是一种不好的代码气味，这意味着该类可能有太多的职责，应该重构以更好地解决适当的关注点分离问题。

Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项。否则，必须在代码使用依赖项的任何地方执行非空检查。setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。因此，通过[JMX MBean](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#jmx)进行管理是 setter 注入的一个引人注目的用例。

使用对特定类最有意义的 DI 样式。有时，在处理您没有源代码的第三方类时，会为您做出选择。例如，如果第三方类没有公开任何 setter 方法，那么构造函数注入可能是 DI 的唯一可用形式。

**依赖解决过程**

容器执行 bean 依赖解析如下：

* `ApplicationContext`使用描述了所有 bean 配置元数据进行创建和初始化。配置元数据可以由 XML、Java 代码或注解指定。
* 对于每个 bean，它的依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示（如果您使用它而不是普通的构造函数）。这些依赖项在实际创建 bean 时提供给 bean。
* 每个属性或构造函数参数都是要设置的值的实际定义，或者是对容器中另一个 bean 的引用。
* 作为值的每个属性或构造函数参数都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下，Spring 可以将以字符串格式提供的值转换为所有内置类型，例如`int`, `long`, `String`,`boolean`等。

Spring 容器在创建容器时验证每个 bean 的配置。但是，在实际创建 bean 之前，不会设置 bean 属性本身。在创建容器时会创建单例范围并设置为预实例化（默认）的 Bean。[范围在Bean Scopes](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes)中定义。否则，只有在请求时才会创建 bean。创建 bean 可能会导致创建 bean 图，因为创建和分配 bean 的依赖项及其依赖项的依赖项（等等）。请注意，这些依赖项之间的解析不匹配可能会出现较晚 - 即在第一次创建受影响的 bean 时。

**循环依赖**

如果您主要使用构造函数注入，则可能会创建无法解决的循环依赖场景。

例如：A类通过构造函数注入需要B类的实例，B类通过构造函数注入需要A类的实例。如果你为类 A 和 B 配置 bean 以相互注入，Spring IoC 容器会在运行时检测到这个循环引用，并抛出一个 `BeanCurrentlyInCreationException`.

一种可能的解决方案是编辑某些类的源代码以由设置器而不是构造器配置。或者，避免构造函数注入并仅使用 setter 注入。也就是说，虽然不推荐，但是可以通过setter注入来配置循环依赖。

与典型情况（没有循环依赖关系）不同，bean A 和 bean B 之间的循环依赖关系强制其中一个 bean 在完全初始化之前注入另一个 bean（典型的先有鸡还是先有蛋的场景）。

您通常可以相信 Spring 会做正确的事情。它在容器加载时检测配置问题，例如对不存在的 bean 和循环依赖项的引用。在实际创建 bean 时，Spring 会尽可能晚地设置属性并解析依赖关系。这意味着，如果在创建该对象或其依赖项之一时出现问题，则正确加载的 Spring 容器稍后可以在您请求对象时生成异常——例如，bean 由于某个丢失或无效的属性而引发异常。某些配置问题的这种潜在延迟可见性就是为什么`ApplicationContext`默认情况下，实现**预实例化单例 bean**。以在实际需要之前创建这些 bean 的一些前期时间和内存为代价，您会在`ApplicationContext`创建这些 bean 时发现配置问题，而不是之后。您仍然可以覆盖此默认行为，以便单例 bean 延迟初始化，而不是急切地预先实例化。

如果不存在循环依赖关系，则当一个或多个协作 bean 被注入到依赖 bean 中时，每个协作 bean 在被注入依赖 bean 之前完全配置。这意味着，如果 bean A 依赖于 bean B，则 Spring IoC 容器在调用 bean A 上的 setter 方法之前完全配置 bean B。换句话说，bean 被实例化（如果它不是预先实例化的单例） )，设置其依赖关系，并调用相关的生命周期方法（例如[配置的 init 方法](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean) 或[InitializingBean 回调方法](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-initializingbean)）。

**依赖注入的例子**

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。Spring XML 配置文件的一小部分指定了一些 bean 定义，如下所示：

```xml
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
```

以下示例显示了相应的`ExampleBean`类：

```java
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
```

在前面的示例中，setter 被声明为与 XML 文件中指定的属性相匹配。以下示例使用基于构造函数的 DI：

```xml
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
```

以下示例显示了相应的`ExampleBean`类：

```java
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
```

bean 定义中指定的构造函数参数用作`ExampleBean`.

现在考虑这个例子的一个变体，其中，Spring 被告知调用`static`工厂方法来返回对象的实例，而不是使用构造函数：

```xml
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
```

以下示例显示了相应的`ExampleBean`类：

```java
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}
```

工厂方法的参数`static`由`<constructor-arg/>`元素提供，就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含`static`工厂方法的类的类型相同（尽管在本例中是这样）。实例（非静态）工厂方法可以以基本相同的方式使用（除了使用`factory-bean`属性而不是`class`属性），因此我们不在这里讨论这些细节。


# 1.4.2. 详细的依赖关系和配置

如上[一节所述](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-collaborators)，您可以将 bean 属性和构造函数参数定义为对其他托管 bean（协作者）的引用或内联定义的值。为此， Spring 的基于 XML 的配置元数据支持其`<property/>`和元素`<constructor-arg/>`中的子元素类型。

**直接值（基元、字符串等）**

元素`<property/>`的`value`属性将属性或构造函数参数指定为人类可读的字符串表示。Spring 的 [转换服务](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#core-convert-ConversionService-API)用于将这些值从 `String` 转换为属性或参数的实际类型。以下示例显示了正在设置的各种值：

```xml
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>
```

以下示例使用[p-namespace](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-p-namespace)进行更简洁的 XML 配置：

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>

</beans>
```

前面的 XML 更简洁。但是，拼写错误是在运行时而不是设计时发现的，除非您在创建 bean 定义时使用支持自动完善属性功能的 IDE（例如[IntelliJ IDEA](https://www.jetbrains.com/idea/)或[Spring Tools for Eclipse ）。](https://spring.io/tools)强烈推荐这种 IDE 帮助。

您还可以配置`java.util.Properties`实例，如下所示：

```xml
<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>
```

Spring 容器通过使用 JavaBeans `PropertyEditor`机制将`<value/>`元素内的文本转换为`java.util.Properties` 实例。这是一个不错的捷径，也是 Spring 团队支持使用嵌套`<value/>`元素而不是`value`属性样式的少数几个地方之一。

**`idref`元素**

`idref`元素只是将容器中另一个 bean 的`id`（字符串值 - 不是引用）传递给`<constructor-arg/>`或元素`<property/>`的一种防错方法 。以下示例显示了如何使用它：

```xml
<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>
```

前面的 bean 定义片段与以下片段完全相同（在运行时）：

```xml
<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>
```

第一种形式比第二种形式更可取，因为使用`idref`标签可以让容器在部署时验证所引用的命名 bean 确实存在。在第二个变体中，不对传递给`client`bean属性`targetName`的值执行验证。只有在实际实例化`client` bean时才会发现拼写错误（很可能是致命的结果） 。如果`client` bean 是一个[原型](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes)bean，那么这个拼写错误和产生的异常可能只有在容器部署很久之后才会被发现。

4.0 bean XSD 不再支持元素上的`local`属性`idref`，因为它不再提供常规`bean`引用的值。在升级到 4.0 架构时将现有`idref local`引用更改为`idref bean` 。

`<idref/>`元素带来价值的常见地方（至少在 Spring 2.0 之前的版本中）是在 bean 定义中的[AOP 拦截器配置中。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pfb-1)`ProxyFactoryBean`在指定拦截器名称时使用`<idref/>`元素可以防止您拼错拦截器 ID。

**对其他 Bean 的引用（协作者）**

`ref`元素是`<constructor-arg/>`或`<property/>` 定义元素中的最后一个元素。在这里，您将 bean 的指定属性的值设置为对容器管理的另一个 bean（协作者）的引用。引用的bean是要设置属性的bean的依赖，在设置属性前根据需要进行初始化。（如果协作者是单例 bean，它可能已经被容器初始化。）所有引用最终都是对另一个对象的引用。范围和验证取决于您是否通过`bean`或`parent`属性指定其他对象的 ID 或名称。

通过`<ref/>`标签的`bean`属性指定目标bean是最通用的形式，它允许创建对同一容器或父容器中的任何bean的引用，而且不用管它是否在同一个XML文件中。属性 `bean`的值可以与目标bean 的属性`id`相同，或者与目标bean 的属性`name`中的值之一相同。以下示例显示了如何使用`ref`元素：

```xml
<ref bean="someBean"/>
```

通过属性`parent`指定目标 bean会创建对当前容器的父容器中的 bean 的引用。`parent`属性的值可以与目标 bean 的`id`属性或目标 bean 的属性`name`中的值之一相同。目标 bean 必须在当前 bean 的父容器中。您应该使用此 bean 引用变体，主要是当您具有容器层次结构并且希望使用与父 bean 同名的代理将现有 bean 包装在父容器中时。以下一对清单显示了如何使用`parent`属性：

```xml
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
```

4.0 bean XSD 不再支持元素上的`local`属性`ref`，因为它不再提供常规`bean`引用的值。在升级到 4.0 架构时需要更改现有`ref local`引用到`ref bean` 。

**内部Bean**

`<property/>`或`<constructor-arg/>`元素中的`<bean/>`元素定义了一个内部 bean，如以下示例所示：

```xml
<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>
```

内部 bean 定义不需要定义的 ID 或名称。如果指定，容器不会使用这样的值作为标识符。容器在创建时也会忽略该`scope`标志，因为内部 bean 始终是匿名的，并且始终使用外部 bean 创建。不可能独立访问内部 bean 或将它们注入到协作 bean 中，而不是注入封闭 bean。

作为一个极端情况，可以从自定义范围接收销毁回调 - 例如，对于包含在单例 bean 中的请求范围内的内部 bean。内部 bean 实例的创建与其包含的 bean 相关联，但销毁回调让它参与请求范围的生命周期。这不是常见的情况。内部 bean 通常只是共享其包含 bean 的范围。

**集合**

`<list/>`、`<set/>`、`<map/>`和`<props/>`元素分别设置 Java `Collection`类型中`List`、`Set`、`Map`的属性和`Properties`参数。以下示例显示了如何使用它们：

```xml
<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>
```

映射键或值或设置值的值也可以是以下任何元素：

```xml
bean | ref | idref | list | set | map | props | value | null
```

**集合合并**

Spring 容器还支持合并集合。应用程序开发人员可以定义父`<list/>`、`<map/>`、`<set/>`或`<props/>`元素，并让子`<list/>` `<map/>` `<set/>` 或`<props/>`元素继承并覆盖父集合中的值。也就是说，子集合的值是合并父集合和子集合的元素的结果，其中子集合元素覆盖父集合中指定的值。

本节关于合并讨论了父子 bean 机制。不熟悉 parent 和 child bean 定义的读者可能希望在继续之前阅读 [相关部分](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-child-bean-definitions)。

以下示例演示了集合合并：

```xml
<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>
```

请注意在`child` bean 定义的`merge=true`属性的`<props/>`元素上使用 `adminEmails`属性。当`child` bean 被容器解析和实例化时，生成的实例有一个`adminEmails` `Properties`集合，其中包含将子集合 `adminEmails`与父`adminEmails`集合合并的结果。以下清单显示了结果：

```properties
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
```

子集合`Properties`的值集从父集合继承所有`<props/>`属性元素，并且子集合的值`support`会覆盖父集合中的值。

这种合并行为同样适用于`<list/>`、`<map/>`和`<set/>` 集合类型。在`<list/>`元素的特定情况下，与`List`集合类型相关联的语义（即`ordered` 值集合的概念）得到维护。父级的值在所有子级列表的值之前。对于`Map`、`Set`和`Properties`集合类型，不存在排序。因此，对于容器内部使用的关联`Map`、`Set`和`Properties`实现类型下的集合类型，没有任何排序语义有效。

**集合合并的限制**

您不能合并不同的集合类型（例如 `Map`和 a `List`）。如果您确实尝试这样做，则会抛出适当的`Exception`。`merge`属性必须在较低的继承的子定义中指定。在父集合定义上指定`merge`属性是多余的，不会导致所需的合并。

**强类型集合**

由于 Java 对泛型类型的支持，您可以使用强类型集合。也就是说，可以声明一个`Collection`类型，使其只能包含（例如）`String`元素。如果使用 Spring 将`Collection`强类型依赖注入到 bean 中，则可以利用 Spring 的类型转换支持，以便强类型实例的`Collection` 元素在添加到`Collection`. 以下 Java 类和 bean 定义显示了如何执行此操作：

```java
public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>
```

当`something` bean 的`accounts`属性准备好注入时，强类型`Map<String, Float>`的元素类型的泛型信息可以通过反射获得。因此，Spring 的类型转换基础结构将各种 value 元素识别为`Float` type ，并将字符串值（`9.99`、`2.75`和 `3.99`）转换为实际`Float`类型。

**Null 和空字符串值**

Spring 将属性等的空参数视为空`Strings`参数。以下基于 XML 的配置元数据片段将该`email`属性设置为空 `String`值 ("")。

```xml
<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>
```

前面的示例等效于以下 Java 代码：

```java
exampleBean.setEmail("");
```

`<null/>`元素处理`null`值。以下清单显示了一个示例：

```xml
<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>
```

上述配置等价于以下 Java 代码：

```java
exampleBean.setEmail(null);
```

**带有 p 命名空间的 XML 快捷方式**

p-namespace 允许您使用`bean`元素的属性（而不是嵌套 `<property/>`元素）来描述协作 bean 或两者的属性值。

Spring 支持[带有命名空间](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#xsd-schemas)的可扩展配置格式，它基于 XML 模式定义。本章讨论的`beans`配置格式在 XML Schema 文档中定义。但是，p-namespace 没有在 XSD 文件中定义，仅存在于 Spring 的核心中。

以下示例显示了解析为相同结果的两个 XML 片段（第一个使用标准 XML 格式，第二个使用 p-namespace）：

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>
```

该示例显示了在 bean 定义中调用的 p-namespace 中的一个`email`属性。这告诉 Spring 包含一个属性声明。如前所述，p-namespace 没有架构定义，因此您可以将属性的名称设置为属性名称。

下一个示例包括另外两个 bean 定义，它们都引用了另一个 bean：

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>
```

此示例不仅包括使用 p 命名空间的属性值，而且还使用特殊格式来声明属性引用。第一个 bean 定义`<property name="spouse" ref="jane"/>`用于创建从 bean `john`到 bean `jane`的引用，而第二个 bean 定义`p:spouse-ref="jane"`用作属性来执行完全相同的操作。在本例中，`spouse`是属性名称，而该`-ref`部分表示这不是直接值，而是对另一个 bean 的引用。

p 命名空间不如标准 XML 格式灵活。例如，声明属性引用的格式与以 结尾的属性冲突`Ref`，而标准 XML 格式则不会。我们建议您仔细选择您的方法并将其传达给您的团队成员，以避免生成同时使用所有三种方法的 XML 文档。

**带有 c 命名空间的 XML 快捷方式**

[与带有 p-namespace 的 XML Shortcut](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-p-namespace)类似，在 Spring 3.1 中引入的 c-namespace 允许内联属性来配置构造函数参数，而不是嵌套`constructor-arg`元素。

以下示例使用`c:`命名空间执行与 [基于构造函数的依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-constructor-injection)相同的操作：

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>
```

命名空间使用与通过名称设置构造函数参数`c:`相同的约定`p:`（bean 引用的`-ref`后缀）。同样，它需要在 XML 文件中声明，即使它没有在 XSD 模式中定义（它存在于 Spring 核心中）。

对于构造函数参数名称不可用的极少数情况（通常是在编译字节码时没有调试信息），您可以使用回退到参数索引，如下所示：

```xml
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>
```

由于 XML 语法，索引表示法需要存在前导`_`，因为 XML 属性名称不能以数字开头（即使某些 IDE 允许这样做）。相应的索引符号也可用于`<constructor-arg>`元素，但不常用，因为声明的简单顺序通常在那里就足够了。

实际上，构造函数解析 [机制](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-ctor-arguments-resolution)在匹配参数方面非常有效，因此除非您确实需要，否则我们建议在整个配置中使用名称表示法。

**复合属性名称**

您可以在设置 bean 属性时使用复合或嵌套属性名称，只要路径的所有组件（最终属性名称除外）都不是`null`. 考虑以下 bean 定义：

```xml
<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>
```

`Something bean` 有一个`fred`属性，该属性有一个 `bob` 属性，该属性有一个 `sammy` 属性，并且最终的 `sammy` 属性被设置为值 `123`。为了使其正常工作，`something` 的 `fred` 属性和 `bob` 属性bean 构造完成后，`fred` 的值不能为 `null`。否则，将引发 `NullPointerException`。


# 1.4.3. 使用depends-on

如果一个 bean 是另一个 bean 的依赖项，那通常意味着一个 bean 被设置为另一个 bean 的属性。通常，您使用基于 XML 的配置元数据中的[`<ref/>` 元素来完成此操作。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-ref-element)但是，有时 bean 之间的依赖关系不那么直接。例如，当需要触发类中的静态初始化程序时，例如用于数据库驱动程序注册。在初始化使用此元素的 bean 之前，`depends-on`属性可以显式强制初始化一个或多个 bean。以下示例使用`depends-on`属性来表达对单个 bean 的依赖：

```xml
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
```

要表达对多个 bean 的依赖关系，请提供 bean 名称列表作为`depends-on`属性值（逗号、空格和分号是有效的分隔符）：

```xml
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
```

> `depends-on`属性既可以指定初始化时间依赖项，也可以指定对应的销毁时间依赖项（仅在[单例bean 的情况下）。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-singleton)定义与给定 bean 的关系的从属`depends-on` bean首先被销毁，然后给定 bean 本身被销毁。这样，`depends-on`也可以控制关机顺序。


# 1.4.4. 延迟初始化的 Bean

默认情况下，作为初始化过程的一部分，`ApplicationContext`实现会急切地创建和配置所有 [单例bean。](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-singleton)通常，这种预实例化是可取的，因为配置或周围环境中的错误会立即发现，而不是几小时甚至几天之后。当这种行为不可取时，您可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在第一次被请求时创建一个 bean 实例，而不是在启动时。

在 XML 中，此行为由`<bean/>` 元素上的属性`lazy-init`控制，如以下示例所示：

```xml
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
```

当前面的配置被 `ApplicationContext`使用时，`lazy`bean 不会在`ApplicationContext`启动时急切地预实例化，而`not.lazy` bean 是急切地预实例化的。

但是，当延迟初始化的 bean 是未延迟初始化的单例 bean 的依赖项时，`ApplicationContext`将在启动时创建延迟初始化的 bean，因为它必须满足单例的依赖项。延迟初始化的 bean 被注入到没有延迟初始化的其他地方的单例 bean 中。

您还可以通过使用元素上的 `default-lazy-init`属性在容器级别控制延迟初始化`<beans/>`，如以下示例所示：

```xml
<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>
```


# 1.4.5. 自动装配协作者

Spring 容器可以自动装配协作 bean 之间的关系。您可以让 Spring 通过检查 `ApplicationContext` 的内容自动为您的 bean 解析协作者（其他 bean）。自动装配具有以下优点：

* 自动装配可以显着减少指定属性或构造函数参数的需要。[（本章其他地方讨论](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-child-bean-definitions)的 bean 模板等其他机制 在这方面也很有价值。）
* 随着对象的发展，自动装配可以更新配置。例如，如果您需要向类添加依赖项，则可以自动满足该依赖项，而无需修改配置。因此，自动装配在开发过程中特别有用，而不会在代码库变得更稳定时否定切换到显式装配的选项。

当使用基于 XML 的配置元数据时（请参阅[依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-collaborators)），您可以使用`<bean/>`元素的`autowire`属性为 bean 定义指定自动装配模式。自动装配功能有四种模式。您指定每个 bean 的自动装配，因此可以选择要自动装配的那些。下表描述了四种自动装配模式：

| 模式            | 解释                                                                                                                                                       |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `no`          | （默认）不自动装配。Bean 引用必须由`ref`元素定义。对于较大的部署，不建议更改默认设置，因为明确指定协作者可以提供更大的控制力和清晰度。在某种程度上，它记录了系统的结构。                                                                |
| `byName`      | 按属性名称自动装配。Spring 寻找与需要自动装配的属性同名的 bean。例如，如果一个 bean 定义被设置为按名称自动装配并且它包含一个`master`属性（即它有一个 `setMaster(..)`方法），那么 Spring 会查找一个命名的 bean 定义`master`并使用它来设置该属性。 |
| `byType`      | 如果容器中恰好存在一个属性类型的 bean，则让属性自动装配。如果存在多个，则会抛出一个致命异常，这表明您可能不会`byType`对该 bean 使用自动装配。如果没有匹配的 bean，则不会发生任何事情（未设置属性）。                                           |
| `constructor` | 类似于`byType`但适用于构造函数参数。如果容器中没有一个构造函数参数类型的 bean，则会引发致命错误。                                                                                                  |

使用`byType`或`constructor`自动装配模式，您可以封装数组和类型化集合。在这种情况下，将提供容器中与预期类型匹配的所有自动装配候选者来满足依赖关系。如果预期的键类型是`String` ，您可以自动装配强类型`Map`实例。自动装配`Map` 实例的值包含与预期类型匹配的所有 bean 实例，并且 `Map`实例的键包含相应的 bean 名称。

**自动装配的限制和缺点**

自动装配在项目中一致使用时效果最佳。如果一般不使用自动装配，开发人员可能会混淆使用它来只装配一个或两个 bean 定义。

考虑自动装配的限制和缺点：

* `property`和`constructor-arg`设置中的显式依赖项总是覆盖自动装配。您不能自动装配简单属性，例如基元、 `Strings`和`Classes`（以及此类简单属性的数组）。此限制是设计使然。
* 自动装配不如显式装配精确。尽管如前表中所述，Spring 会小心避免猜测可能会产生意想不到的结果的歧义。Spring 管理的对象之间的关系不再明确记录。
* 从 Spring 容器生成文档的工具可能无法使用接线信息。
* 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型匹配。对于数组、集合或 `Map`实例，这不一定是问题。但是，对于期望单个值的依赖项，这种歧义不会被任意解决。如果没有唯一的 bean 定义可用，则会引发异常。

在后一种情况下，您有多种选择：

* 放弃自动装配以支持显式装配。
* 通过将属性`autowire-candidate`设置为`false` 来避免对 bean 定义进行自动装配，如[下一节所述](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-autowire-candidate)。
* 通过将其`<bean/>`元素的`primary`属性设置为`true` ，将单个 bean 定义指定为主要候选者 。
* 使用基于注解的配置实现更细粒度的控制，如[基于注解的容器配置](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-annotation-config)中所述。

**从自动装配中排除 Bean**

在每个 bean 的基础上，您可以从自动装配中排除 bean。在 Spring 的 XML 格式中，将`<bean/>`元素的`autowire-candidate`属性设置为`false`. 容器使自动装配基础设施无法使用特定的 bean 定义（包括注解样式配置，例如[`@Autowired`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-autowired-annotation)）。

`autowire-candidate`属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用，即使指定的 bean 未标记为自动装配候选者，也会得到解析。因此，如果名称匹配，按名称自动装配仍然会注入一个 bean。

您还可以根据与 bean 名称的模式匹配来限制自动装配候选者。顶级`<beans/>`元素在其 `default-autowire-candidates`属性中接受一个或多个模式。例如，要将自动装配候选状态限制为名称`*Repository` 结尾的任何 bean ，请提供`Repository`. 要提供多种模式，请在逗号分隔的列表中定义它们。bean 定义属性`autowire-candidate`的显式值 `true`或`false`始终优先。对于此类 bean，模式匹配规则不适用。

这些技术对于您永远不想通过自动装配注入其他 bean 的 bean 很有用。这并不意味着排除的 bean 本身不能通过使用自动装配来配置。相反，bean 本身不是自动装配其他 bean 的候选对象。


# 1.4.6. 方法注入

在大多数应用场景中，容器中的大多数 bean 都是 [单例](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-singleton)的。当一个单例 bean 需要与另一个单例 bean 协作或非单例 bean 需要与另一个非单例 bean 协作时，您通常通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。当 bean 生命周期不同时，就会出现问题。假设单例 bean A 需要使用非单例（原型）bean B，可能在 A 上的每个方法调用上。容器只创建一次单例 bean A，因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供一个新的 bean B 实例。

一个解决方案是放弃一些控制反转。您可以通过实现[`ApplicationContextAware`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware)接口将 bean A 织入到容器，并在每次bean A 需要时通过[`getBean("B")`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-client)方法调用容器来请求（通常是新的）bean B 实例。以下示例显示了这种方法：

```java
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
```

前面是不可取的，因为业务代码知道并耦合到 Spring 框架。方法注入是 Spring IoC 容器的一项高级功能，可让您干净地处理此用例。

[您可以在此博客条目](https://spring.io/blog/2004/08/06/method-injection/)中阅读有关方法注入动机的更多信息 。

**查找方法注入**

查找方法注入是容器覆盖容器管理的 bean 上的方法并返回容器中另一个命名 bean 的查找结果的能力。查找通常涉及一个原型 bean，如上[一节](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-method-injection)中描述的场景。Spring 框架通过使用 CGLIB 库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。

要使这种动态子类化工作，Spring bean 容器子类的类不能是`final`，要覆盖的方法也不能是`final` 。对具有`abstract`方法的类进行单元测试需要您自己子类化该类并提供该`abstract`方法的存根实现。组件扫描也需要具体的方法，这需要具体的类来拾取。另一个关键限制是查找方法不适用于工厂方法，特别是不适用于配置类中的`@Bean`方法，因为在这种情况下，容器不负责创建实例，因此无法创建运行时生成的子类苍蝇。

在前面代码片段中的`CommandManager`类的情况下，Spring 容器会动态覆盖`createCommand()` 方法的实现。类`CommandManager`没有任何 Spring 依赖项，如重新设计的示例所示：

```java
package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
```

在包含要注入的方法的客户端类中（在本例中为`CommandManager` ），要注入的方法需要以下形式的签名：

```xml
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
```

如果方法是`abstract`，则动态生成的子类实现该方法。否则，动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例：

```xml
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>
```

每当需要 `myCommand bean` 的新实例时，标识为 `commandManager` 的 bean 都会调用自己的`createCommand()`方法。如果实际需要的话，您必须小心地将 `myCommand bean` 部署为原型。如果是单例，则每次都会返回 `myCommand bean` 的相同实例。

或者，在基于注解的组件模型中，您可以通过注解声明查找方法`@Lookup`，如以下示例所示：

```java
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}
```

或者，更惯用的说法是，您可以依赖于根据查找方法声明的返回类型解析目标 bean：

```java
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}
```

请注意，您通常应该使用具体的存根实现声明此类带注解的查找方法，以使它们与默认情况下忽略抽象类的 Spring 组件扫描规则兼容。此限制不适用于显式注册或显式导入的 bean 类。

访问不同范围的目标 bean 的另一种方法是`ObjectFactory`/ `Provider`注入点。请参阅[Scoped Beans as Dependencies](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other-injection)。您可能还会发现`ServiceLocatorFactoryBean`（在 `org.springframework.beans.factory.config`包中）很有用。

**任意方法替换**

与查找方法注入相比，一种不太有用的方法注入形式是能够用另一种方法实现替换托管 bean 中的任意方法。在您真正需要此功能之前，您可以放心地跳过本节的其余部分。

使用基于 XML 的配置元数据，您可以使用`replaced-method`元素将现有方法实现替换为另一个，用于已部署的 bean。考虑下面的类，它有一个我们想要重写的`computeValue`方法：

```java
public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}
```

实现`org.springframework.beans.factory.support.MethodReplacer` 接口的类提供了新的方法定义，如以下示例所示：

```java
/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}
```

部署原始类并指定方法覆盖的 bean 定义类似于以下示例：

```xml
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
```

您可以在`<arg-type/>`元素中使用一个或多个`<replaced-method/>`元素 来指示被覆盖方法的方法签名。只有当方法被重载并且类中存在多个变体时，参数的签名才是必需的。为方便起见，参数的类型字符串可以是完全限定类型名称的子字符串。例如，以下所有匹配 `java.lang.String`：

```java
java.lang.String
String
Str
```

因为参数的数量通常足以区分每个可能的选择，所以这个快捷方式可以节省大量的输入，让您只输入与参数类型匹配的最短字符串。


# 2. Resources

本章介绍 Spring 如何处理资源以及如何在 Spring 中使用资源。它包括以下主题：

* [介绍](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-introduction)
* [`Resource`界面](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-resource)
* [内置`Resource`实现](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations)
* [`ResourceLoader`界面](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-resourceloader)
* [`ResourcePatternResolver`界面](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-resourcepatternresolver)
* [`ResourceLoaderAware`界面](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-resourceloaderaware)
* [资源作为依赖](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-as-dependencies)
* [应用程序上下文和资源路径](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-app-ctx)

####

####

####

####

####

####

####


# 2.1. 介绍

不幸的是，Java 的标准 `java.net.URL` 类和各种 `URL` 前缀的标准处理程序不足以满足对低级资源的所有访问。例如，没有可用于访问需要从类路径或相对于 `ServletContext` 获取的资源的标准化`URL`实现。虽然可以为专门的 URL 前缀注册新的处理程序（类似于`http:`等前缀的现有处理程序），但这通常相当复杂，并且 URL 接口仍然缺乏一些理想的功能，例如检查是否存在的方法所指向的资源的名称。


# 2.2. Resource接口

位于`org.springframework.core.io.`包中的Spring`Resource`接口旨在成为一个更强大的接口，用于抽象对低级资源的访问。以下清单提供了`Resource`界面的概述。有关详细信息，请参阅 [`Resource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/io/Resource.html)javadoc。

```java
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}
```

正如`Resource`接口的定义所示，它扩展了`InputStreamSource` 接口。以下清单显示了`InputStreamSource` 接口的定义：

```java
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}
```

`Resource`接口中一些最重要的方法是：

* `getInputStream()`：定位并打开资源，返回一个`InputStream`用于从资源中读取。预计每次调用都会返回一个新的 `InputStream`. 调用者有责任关闭流。
* `exists()`：返回一个`boolean`指示此资源是否实际以物理形式存在的值。
* `isOpen()`：返回一个`boolean`指示此资源是否表示具有打开流的句柄。如果为`true`， 则`InputStream`不能多次读取，必须只读取一次然后关闭以避免资源泄漏。对于所有常用资源实现返回 `false`（`InputStreamResource` 除外）。
* `getDescription()`：返回此资源的描述，用于在使用该资源时输出错误。这通常是完全限定的文件名或资源的实际 URL。

其他方法让您获得表示资源的实际`URL`或`File`对象（如果底层实现兼容并支持该功能）。

`Resource`接口的一些实现还为支持写入的资源实现了扩展 [`WritableResource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/io/WritableResource.html)接口。

Spring 本身广泛使用`Resource`抽象，在需要资源时作为许多方法签名中的参数类型。某些 Spring API 中的其他方法（例如各种 `ApplicationContext` 实现的构造函数）采用 `String`，该 `String` 以原始或简单的形式用于创建适合该上下文实现的资源，或者通过 `String` 路径上的特殊前缀，让调用者指定必须创建和使用特定的资源实现。

虽然`Resource`接口在 Spring 和 Spring 中被大量使用，但实际上在您自己的代码中将其本身用作通用实用程序类非常方便，用于访问资源，即使您的代码不知道或不关心spring任何其他部分。虽然这会将您的代码与 Spring 耦合，但它实际上只将它耦合到这一小部分实用程序类，它可以作为更强大的替代品，`URL`并且可以被认为等同于您将用于此目的的任何其他库。

`Resource`抽象不会取代功能。它尽可能地包装它。例如，UrlResource 包装 URL 并使用包装的 URL 来完成其工作。


# 2.3. 内置Resource实现

Spring 包括几个内置的`Resource`实现：

* [`UrlResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-urlresource)
* [`ClassPathResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-classpathresource)
* [`FileSystemResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-filesystemresource)
* [`PathResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-pathresource)
* [`ServletContextResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-servletcontextresource)
* [`InputStreamResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-inputstreamresource)
* [`ByteArrayResource`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-implementations-bytearrayresource)

有关 Spring `Resource`中可用实现的完整列表，请参阅javadoc [`Resource`](https://docs.spring.io/spring-framework/docs/5.3.22/javadoc-api/org/springframework/core/io/Resource.html)的“所有已知的实现类”部分 。

**2.3.1. `UrlResource`**

`UrlResource`包装了`java.net.URL`并可用于访问通常可通过 URL 访问的任何对象，例如文件、HTTPS 目标、FTP 目标等。所有 URL 都有一个标准化的`String`表示，因此使用适当的标准化前缀来指示一个 URL 类型与另一个 URL 类型。这包括 `file:`访问文件系统路径、`https:`通过 HTTPS 协议访问资源、`ftp:`通过 FTP 访问资源等。

`UrlResource`是由 Java 代码通过显式使用`UrlResource`构造函数创建的，但通常是在调用 API 方法时隐式创建的，该方法采用`String` 表示路径的参数。对于后一种情况，JavaBeans`PropertyEditor` 最终决定创建哪种类型`Resource`。如果路径字符串包含一个众所周知的（对属性编辑器而言）前缀（例如`classpath:`），它会为该前缀创建一个适当的专用化`Resource`。但是，如果它不能识别前缀，它会假定该字符串是标准 URL 字符串并创建一个`UrlResource`.

**2.3.2. `ClassPathResource`**

此类表示应从类路径获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。

如果类路径资源驻留在文件系统中，则此资源实现支持解析为 `java.io.File`，但不支持驻留在 jar 中且尚未（通过 servlet 引擎或任何环境）扩展到文件系统。为了解决这个问题，各种 `Resource` 实现始终支持解析为 `java.net.URL`。

`ClassPathResource`是由 Java 代码通过显式使用`ClassPathResource` 构造函数创建的，但通常是在调用 API 方法时隐式创建的，该方法采用 `String`表示路径的参数。对于后一种情况，JavaBeans `PropertyEditor`识别字符串路径上的特殊前缀 ,`classpath:`，并在这种情况下创建 `ClassPathResource`。

**2.3.3. `FileSystemResource`**

这是`java.io.File`句柄的`Resource`实现。它还支持 `java.nio.file.Path`句柄，应用 Spring 的标准基于字符串的路径转换，但通过`java.nio.file.Files`API 执行所有操作。对于纯 `java.nio.path.Path`基于支持，请改用 `PathResource`。`FileSystemResource` 支持解析为`File`和 `URL`。

**2.3.4. `PathResource`**

这是句柄的`Resource`实现，通过API 执行所有操作和转换。它支持解析为 a和 as a并且还实现了扩展接口。 实际上是具有不同行为的纯基于替代方案。`java.nio.file.Path``Path``File``URL``WritableResource``PathResource``java.nio.path.Path``FileSystemResource``createRelative`

**2.3.5. `ServletContextResource`**

这是`ServletContext`资源的 `Resource` 实现，它解释相关 Web 应用程序根目录中的相对路径。

它始终支持流访问和 URL 访问，但仅在扩展 Web 应用程序存档且资源物理位于文件系统上时才允许`java.io.File`访问。它是否被扩展并在文件系统上或直接从 JAR 或其他地方（如数据库）访问（这是可以想象的）实际上取决于 Servlet 容器。

**2.3.6. `InputStreamResource`**

`InputStreamResource`是`Resource`给定`InputStream` 的实现。只有在没有特定`Resource`实现适用时才应使用它。特别是，在可能的情况下，首选`ByteArrayResource`或任何基于文件的`Resource` 实现。

与其他`Resource`实现相比，这是一个已打开资源的描述符。因此，它`isOpen()`方法返回`true`。 如果您需要将资源描述符保存在某处或需要多次读取流，请不要使用它。

**2.3.7. `ByteArrayResource`**

这是`Resource`给定字节数组的实现。它为给定的字节数组创建一个 `ByteArrayInputStream`。

它对于从任何给定的字节数组加载内容很有用，而不必求助于单次使用`InputStreamResource`。


# 2.4. ResourceLoader接口

`ResourceLoader`接口旨在由可以返回（即加载）`Resource`实例的对象实现。以下清单显示了`ResourceLoader` 接口定义：

```java
public interface ResourceLoader {

    Resource getResource(String location);

    ClassLoader getClassLoader();
}
```

所有应用程序上下文都实现了该`ResourceLoader`接口。因此，所有应用程序上下文都可以用于获取`Resource`实例。

当您调用`getResource()`特定的应用程序上下文，并且指定的位置路径没有特定的前缀时，您将返回`Resource`适合该特定应用程序上下文的类型。例如，假设下面的代码片段是针对一个`ClassPathXmlApplicationContext`实例运行的：

```java
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
```

针对 `ClassPathXmlApplicationContext`，该代码返回 `ClassPathResource`。如果对`FileSystemXmlApplicationContext`实例运行相同的方法，它将返回一个`FileSystemResource`. 对于 `WebApplicationContext`，它将返回 `ServletContextResource`。它同样会为每个上下文返回适当的对象。

因此，您可以以适合特定应用程序上下文的方式加载资源。

另一方面，您也可以通过指定`classpath:`特殊前缀来强制使用`ClassPathResource`，无论应用程序上下文类型如何，如以下示例所示：

```java
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
```

同样，您可以通过指定任何标准 `java.net.URL`前缀来强制使用 `UrlResource`。以下示例使用`file`和`https`前缀：

```java
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
```

```java
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
```

下表总结了将`String`对象转换为`Resource` 对象的策略：

| 前缀         | 例子                               | 解释                                                                                                                                                                 |
| ---------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| classpath: | `classpath:com/myapp/config.xml` | 从类路径加载。                                                                                                                                                            |
| file:      | `file:///data/config.xml`        | 从文件系统加载为 `URL`。另请参阅[`FileSystemResource`注意事项](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-filesystemresource-caveats)。 |
| https:     | `https://myserver/logo.png`      | 加载为`URL`.                                                                                                                                                          |
| 无          | `/data/config.xml`               | 取决于底层`ApplicationContext`。                                                                                                                                         |

####


# 2.5. ResourcePatternResolver接口

`ResourcePatternResolver`接口是接口`ResourceLoader`的扩展，它定义了将位置模式（例如，Ant 风格的路径模式）解析为`Resource`对象的策略。

```java
public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;
}
```

从上面可以看出，这个接口还为类路径中所有匹配的资源定义了一个特殊的`classpath*:`资源前缀。请注意，在这种情况下，资源位置应该是没有占位符的路径，例如 `classpath*:/config/beans.xml`. JAR 文件或类路径中的不同目录可以包含多个具有相同路径和相同名称的文件。有关使用资源前缀`classpath*:`通配符支持的更多详细信息，请参阅[应用程序上下文构造函数资源路径](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-app-ctx-wildcards-in-resource-paths)中的通配符及其小节。

可以检查传入的`ResourceLoader`（例如，通过 [`ResourceLoaderAware`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#resources-resourceloaderaware)语义提供的）是否也实现了此扩展接口。

`PathMatchingResourcePatternResolver` 是一个独立的实现，可以在`ApplicationContext`外部使用，并且也可以被 `ResourceArrayPropertyEditor` 用于填充 `Resource[]` bean 属性。 `PathMatchingResourcePatternResolver`能够将指定的资源位置路径解析为一个或多个匹配的`Resource`对象。源路径可以是一个简单的路径，它与目标资源有一对一的映射，或者可以包含特殊的 `classpath*:` 前缀和/或内部 Ant 风格的正则表达式（使用 Spring 的 org.springframework.util 进行匹配） .AntPathMatcher 实用程序）。后者实际上都是通配符。

任何标准 `ApplicationContext` 中的默认 `ResourceLoader` 实际上都是 `PathMatchingResourcePatternResolver` 的实例，它实现了 `ResourcePatternResolver` 接口。 `ApplicationContext` 实例本身也是如此，它也实现了 `ResourcePatternResolver` 接口并委托给默认的 `PathMatchingResourcePatternResolver`。


# 2.6. ResourceLoaderAware接口

`ResourceLoaderAware`接口是一个特殊的回调接口，用于标识期望提供`ResourceLoader`引用的组件。以下清单显示了`ResourceLoaderAware`接口的定义：

```java
public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}
```

当一个类实现`ResourceLoaderAware`并部署到应用程序上下文（作为 Spring 管理的 bean）中时，它被应用程序上下文识别为`ResourceLoaderAware`。然后应用程序上下文调用`setResourceLoader(ResourceLoader)`，将自己作为参数提供（请记住，Spring 中的所有应用程序上下文都实现了该`ResourceLoader`接口）。

由于 `ApplicationContext`是一个 `ResourceLoader`，因此 bean 也可以实现 `ApplicationContextAware`接口并直接使用提供的应用程序上下文来加载资源。但是，一般来说，如果您只需要专用接口，最好使用`ResourceLoader` 专用接口。该代码将仅与资源加载接口（可以认为是实用程序接口）耦合，而不与整个 Spring `ApplicationContext`接口耦合。

在应用程序组件中，您还可以依赖 `ResourceLoader` 的自动装配作为实现`ResourceLoaderAware`接口的替代方法。传统的构造函数和 byType 自动装配模式（如自动装配协作者中所述）能够分别为构造函数参数或 setter 方法参数提供 `ResourceLoader`。为了获得更大的灵活性（包括自动装配字段和多个参数方法的能力），请考虑使用基于注解的自动装配功能。在这种情况下，只要相关字段、构造函数或方法带有 `@Autowired` 注解，`ResourceLoader` 就会自动装配到需要 `ResourceLoader` 类型的字段、构造函数参数或方法参数中。有关更多信息，请参阅使用@Autowired。

要为包含通配符或使用特殊`classpath*:`资源前缀的资源路径加载一个或多个 Resource 对象，请考虑将 `ResourcePatternResolver` 实例（而不是 ResourceLoader）自动装配到应用程序组件中。




---

[Next Page](/llms-full.txt/1)

