读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》应用程序打包和部署
在本章中,我们将介绍以下主题:
- Creating a Spring Boot executable JAR
- Creating Docker images
- Building self-executing binaries
- Spring Boot environment configuration, hierarchy, and precedence
- Adding a custom PropertySource to the environment using EnvironmentPostProcessor
- Externalizing an environmental configuration using property files
- Externalizing an environmental configuration using environment variables
- Externalizing an environmental configuration using Java system properties
- Externalizing an environmental configuration using JSON
- Setting up Consul
- Externalizing an environmental configuration using Consul and envconsul
除非正在使用,否则应用程序有什么用?在当今时代——当 DevOps 已成为进行软件开发的方式时,当云为王时,当构建微服务被认为是要做的事情时——很多注意力都集中在应用程序如何打包、分发、并部署在他们指定的环境中。
十二因素应用程序方法发挥在定义如何 现代 软件即服务 (SaaS ) 应用程序应该构建 并部署。关键原则之一是将环境配置定义与环境中的应用和存储分开。 Twelve-Factor App 方法论还支持依赖项的隔离和捆绑、开发与生产的对等,以及应用程序的易于部署和可处置性等。
Note
十二因子应用方法可以在 http://12factor.net/ 找到。
DevOps 模型还鼓励我们对我们的应用程序拥有完全的所有权,从编写和测试代码一直到构建和部署它。如果我们要承担这种所有权,我们需要确保维护和间接费用不会过多,并且不会占用我们开发新功能的主要任务的太多时间。这可以通过拥有干净、定义明确和隔离的可部署工件来实现,这些工件是自包含、自执行的,并且可以部署在任何环境中而无需重新构建。
以下秘籍将引导我们完成所有必要的步骤,以实现轻松部署和维护的目标,同时拥有干净优雅的代码。
Spring 如果不提供一种很好的方式来打包整个应用程序(包括其所有依赖项、资源、等等在一个复合的可执行 JAR 文件中。创建 JAR 文件后,只需运行 java -jar <name>.jar
命令即可启动它。
我们将继续使用我们在前几章中构建的应用程序代码,并将添加必要的功能来打包它。让我们继续看看如何创建 Spring Boot Uber JAR。
- Let's go to our code directory from Chapter 24, Application Testing, and execute
./gradlew clean build
- With the Uber JAR built, let's launch the application by executing
java -jar build/libs/ch6-0.0.1-SNAPSHOT.jar
- This will result in our application running in the JAR file with the following console output:
如您所见,获取 packaged 可执行 JAR 文件相当简单。所有的魔法都已经编码并作为 Spring Boot Gradle 插件的一部分提供给我们。插件的添加增加了许多任务,允许我们打包 Spring Boot 应用程序、运行它并构建 JAR、TAR、WAR 文件等。例如,我们在本书中一直使用的 bootRun
任务是由 Spring Boot Gradle 插件等提供的。我们可以通过执行 ./gradlew tasks
来查看可用的 Gradle 任务的完整列表。当我们运行这个命令时,我们将得到以下输出:
前面的输出不完整;我已经排除了不相关的任务组,例如 IDE、文档等,但您会在控制台上看到它们。在任务列表中,我们会看到bootRun
、bootJar
等任务。这些任务已由 Spring Boot Gradle 插件添加,执行它们会将所需的 Spring Boot 步骤添加到构建管道中。执行 ./gradlew tasks --all
可以看到实际的任务依赖,不仅打印可见任务,还打印依赖的内部任务和任务依赖关系。例如,当我们运行 build
任务时,以下所有依赖任务也被执行:
可以看到 build
任务会执行 assemble
任务,而后者又会调用 bootJar
,实际上是在其中创建 Uber JAR。
该插件还提供了许多非常有用的配置选项。虽然我不打算详细介绍所有这些,但我会提到我认为非常有用的两个:
此配置允许我们指定可执行 JAR 文件 classifier
以及 JAR baseName
,允许常规 JAR 仅包含应用程序代码和名称中带有 classifier
的可执行 JAR,bookpub-0.0.1-SNAPSHOT-exec.jar
。
另一个有用的配置选项允许我们指定哪些依赖 JAR 需要解包,因为由于某种原因,它们不能作为嵌套的内部 JAR 包含在内。当您需要系统 Classloader
中可用的东西时,这非常方便,例如通过启动系统设置自定义 SecurityManager
特性:
在此示例中,当应用程序启动时,some-jar-name-1.0.3.jar
依赖项的内容将被解压缩到文件系统上的临时文件夹中。
码头工人,码头工人,码头工人!在我参加的所有会议和技术聚会中,我听到这个短语越来越多。 Docker的到来受到了社区张开双臂的欢迎,并立即成为热门话题。 Docker 生态系统一直在迅速扩展,许多其他公司提供服务、支持和补充框架,例如 Apache Mesos、 Amazon Elastic Beanstalk、ECS和 Kubernetes,仅举几例。甚至微软也在其 Azure 云服务中提供 Docker 支持,并与 Docker 合作将 Docker 引入 Windows 操作系统。
Docker 大受欢迎的原因在于它能够以自包含容器的形式打包和部署应用程序。容器比传统的成熟虚拟机更轻量级。它们中的多个可以在单个操作系统实例之上运行,因此与传统 VM 相比,可以在相同硬件上部署的应用程序数量增加。
在这个秘籍中,我们将了解如何将 Spring Boot 应用程序打包为 Docker 镜像,以及如何部署和运行它。
构建一个 Docker 镜像并在你的开发机器上运行它是可行的,但不如与世界分享它有趣。您需要将它发布到某个地方才能部署,尤其是当您考虑将它与 Amazon 或其他类似云的环境一起使用时。幸运的是,Docker 不仅为我们提供了容器解决方案,还为我们提供了一个存储库服务,Docker Hub,位于 https://hub.docker.com, 我们可以在其中创建存储库并发布我们的 Docker 镜像。所以把它想象成 Docker 的 Maven Central。
- The first step will be to create an account on Docker Hub so that we can publish our images. Go to https://hub.docker.com and create an account. You can also use your GitHub account and log in using it if you have one.
- Once you have an account, we will need to create a repository named
springbootcookbook
. - With this account created, now is the time to build the image. For this, we will use one of the Gradle Docker plugins. We will start by changing
build.gradle
to modify thebuildscript
block with the following change:
- We will also need to apply this plugin by adding the
apply plugin: 'docker'
directive to thebuild.gradle
file. - We also need to explicitly add the
application
plugin tobuild.gradle
as well, since it is no longer automatically included by the Spring Boot Gradle plugin. - Add
apply plugin: 'application'
to the list of plugins in thebuild.gradle
file. - Lastly, we will need to add the following Docker configuration to the
build.gradle
file as well:
- Assuming that you already have Docker installed on your machine, we can proceed to creating the image by executing
./gradlew clean distDocker
. - For Docker installation instructions, please visit the tutorial that is located at https://docs.docker.com/installation/#installation. If everything has worked out correctly, you should see the following output:
- We can also execute the following Docker images command so as to see the newly created image:
- With the image built successfully, we are now ready to start it in Docker by executing the following command:
- After the container has started, we can query the Docker registry for the port bindings so that we can access the HTTP endpoints for our service. This can be done via the
docker ps
command. If the container is running successfully, we should see the following result (names and ports will vary):
- From this output, we can tell that the port mapping for the internal port
8080
has been set up to be32778
(your port will vary for every run). Let's openhttp://localhost:32778/books
in the browser to see our application in action, as shown in the following screenshot:
Note
如果您使用带有 boot2docker
的 macOS X,那么您将不会在本地运行 Docker 容器。在这种情况下,您将使用 boot2docker ip
而不是本地主机来连接到应用程序。有关如何使 boot2docker
集成更容易的更多提示,请访问http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-指南。还可以使用由 Ian Sinnott 慷慨创建的漂亮 Docker 外观,它会自动启动 boot2docker 并处理环境变量。要获取包装器,请转到 https://gist.github.com/iansinnott/0a0c212260386bdbfafb 。
在前面的示例中,我们看到让我们的 build
将应用程序打包到 Docker 容器。额外的 Gradle-Docker 插件完成了 Dockerfile
创建、镜像构建和发布的大部分工作;我们所要做的就是给它一些关于我们想要图像的内容和方式的说明。因为 Spring Boot Gradle 插件使用 boot
发行版,Gradle-Docker 插件不知道它需要使用引导的 TAR 存档。为了解决这个问题,我们重写了 distDocker
任务。让我们详细检查这些说明:
- The
group
anddescription
attributes merely help with displaying the task properly when the./gradlew tasks
command is executed. - The
inputs.files project.bootDistTar
directive is very important. This is what instructs thedistDocker
task to use the TAR archive created by the Spring Boot distribution, instead of the generic one. - The
def installDir = "/" + project.bootDistTar.archiveName - ".${project.bootDistTar.extension}"
directive is creating a variable, containing the directory where the untarred artifacts will be placed inside the Docker container. - The
exposePort
directive tells the plugin to add anEXPOSE <port>
instruction to the Dockerfile so that when our container is started, it will expose these internal ports to the outside via port mapping. We saw this mapping while running thedocker ps
command. - The
addFile
directive tells the plugin to add anADD <src> <dest>
instruction to the Dockerfile so that when the container is being built, we will copy the file from the source filesystem in the filesystem in the container image. In our case, we will need to copy the.keystore
certificate file that we configured in one of our previous recipes for the HTTPS connector, which we instructed intomcat.https.properties
to be loaded from${user.home}/.keystore
. Now, we need it to be in the/root/ directory
directory as, in the container, our application will be executed under the root. (This can be changed with more configurations.)
Note
Gradle-Docker 插件默认使用项目名称作为镜像的名称。反过来,项目名称是 Gradle 从项目的目录名称中推断出来的,除非配置了显式的属性值。由于代码示例适用于 Chapter 25,应用程序打包和部署 项目目录被命名为ch25
,因此是图像的名称。项目名称可以通过在 gradle.properties
中添加 name='some_project_name'
来显式配置。
如果查看生成的 Dockerfile,该文件位于项目根目录的 build/docker/
目录中,您将看到以下两条指令:
ADD
指令添加由 bootDistTar
任务生成的 TAR 应用程序存档,并包含捆绑为 tarball 的应用程序。我们甚至可以通过执行 tar tvf build/distributions/ch6-boot-0.0.1-SNAPSHOT.tar
来查看生成的tarball的内容。在容器的构建过程中,TAR 文件的内容会被提取到容器的 /
目录中,然后用于启动应用程序。
其后是 ENTRYPOINT
指令。这告诉 Docker 执行 /ch6-boot-0.0.1-SNAPSHOT/bin/ ch6
,我们将其视为 tarball 内容的一部分,一旦容器启动,就会自动启动我们的应用程序。
Dockerfile中的第一行,即FROM aglover/java8-pier
,是使用aglover/java8-pier 镜像,其中包含安装了 Java 8 的 Ubuntu 操作系统作为我们容器的基础镜像,我们将在其上安装我们的应用程序。此映像来自 Docker Hub 存储库,由插件自动使用,但如果需要,可以通过配置设置进行更改。
如果您在 Docker Hub 上创建了一个帐户,我们还可以将创建的 Docker 镜像发布到注册表。作为公平的警告,生成的图像可能有数百兆字节大小,因此上传可能需要一些时间。要发布此图像,我们需要将标签更改为 tag "<docker hub 用户名>/<docker hub 存储库名称>"
并添加 push true
设置到 build.gradle
中的 distDocker
任务定义:
tag
属性设置创建的图像标签,默认情况下,插件假定它驻留在 Docker 集线器存储库。如果 push
配置设置为 true
,它将在此处发布它,就像我们的例子一样。
Note
有关所有 Gradle-Docker 插件配置选项的完整列表,请查看 < span>https://github.com/Transmode/gradle-docker GitHub 项目页面。
启动 Docker 映像时,我们使用 -d
和 -P
命令行参数。它们的用途如下:
-d
: This argument indicates the desire to run the container in a detached mode where the process starts in the background-P
: This argument instructs Docker to publish all the internally exposed ports to the outside so that we can access them
Note
有关所有可能的命令行选项的详细说明,请参阅 https://docs.docker.com/reference/commandline/cli/。
从 Spring Boot 1.3 版开始,Gradle 和 Maven 插件支持生成真正的可执行二进制文件的选项。这些看起来像普通的 JAR 文件,但 JAR 的内容与包含命令构建逻辑的启动脚本融合在一起,并且能够自行启动而无需执行 java - jar file.jar
命令显式。此功能非常方便,因为它允许轻松配置 Linux 自动启动服务,例如 init.d
或 systemd
,以及launchd
在 macOS X 上。
对于这个秘籍,我们将使用我们现有的应用程序构建。我们将研究如何创建自启动可执行 JAR 文件以及如何修改默认启动脚本以添加对自定义 JVM 启动参数的支持,例如 -D
start设置系统属性、JVM 内存、垃圾收集和其他设置。
对于这个秘籍,确保 build.gradle
使用 Spring Boot 2.0.0 或更高版本。如果不是,则在 buildscript
配置块中更改以下设置:
Spring Boot 版本的相同升级也应该在 db-counter-starter/build.gradle
文件中完成。
- Building a default self-executing JAR file is very easy; actually, it is done automatically once we execute the
./gradlew clean bootJar
command. - We can proceed to launch the created application simply by invoking
./build/libs/bookpub-0.0.1-SNAPSHOT.jar
.
- In an enterprise environment, it is rare that we are satisfied with the default JVM launch arguments as we often need to tweak the memory settings, GC configurations, and even pass the startup system properties in order to ensure that we are using the desired version of the XML parser or a proprietary implementation of class loader or security manager. To accomplish those needs, we will modify the default
launch.script
file to add support for the JVM options. Let's start by copying the defaultlaunch.script
file from the https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script Spring Boot GitHub repository in the root of our project.
launch.script
文件仅在 Linux 和 OS X 环境中受支持。如果您希望为 Windows 制作自执行 JAR,则需要提供您自己的 launch.script
文件,该文件专为 Windows shell 命令执行而定制。好消息是它是唯一需要的特殊东西。本秘籍中的所有说明和概念也可以在 Windows 上正常运行,前提是兼容的 launch.script
模板。
- We will modify the copied
launch.script
file and add the following content right above the line 142 mark (this is showing only the relevant part of the script so as to condense the space):
- With the custom
launch.script
file in place, we will need to add the options setting to ourbuild.gradle
file with the following content:
- We can also build the self-starting executable JAR by running the
./gradlew clean bootJar
command and then executing./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar
in order to launch our application. We should expect to see a similar result in JConsole.
- Alternatively, we can also use the
JAVA_OPTS
environment variable to override some of the JVM arguments. Say we want to change the minimum memory heap size to 128 megabytes. We can launch our application using theJAVA_OPTS=-Xmx128m ./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar
command and this would show us the following effect in JConsole:
通过对 launch.script
的小定制,我们能够create一个自执行的可部署应用程序,打包为一个独立的 JAR 文件,除此之外,还可以配置它,以便使用各种特定于操作系统的自动启动框架启动。
Spring Boot Gradle 和 Maven 插件为我们提供了许多参数自定义选项,甚至可以在 launch.script
中嵌入类似 mustache 的模板占位符,以后可以将其替换为构建期间的值。我们利用此功能将我们的 JVM 参数注入到使用 launchScript{properties}
配置设置的文件中。
在我们自定义版本的 launch.script
中,我们添加了 jvmopts="{{jvm_options:}}"
行,它将在构建和打包期间被 jvm_options
参数的值替换。此参数在我们的 build.gradle
文件中声明为 launchScript.properties
参数 :launchScript{properties 'jvm_options':applicationDefaultJvmArgs.join('')}
。
JVM 参数可以是硬编码的,但最好在我们的应用程序如何开始使用 bootRun
任务和它从自执行 JAR 启动时如何启动之间保持一致。为此,我们将使用与 arguments 相同的 applicationDefaultJvmArgs
集合我们将为 bootRun
执行目的进行定义,仅将所有不同的参数折叠在由空格分隔的单行文本中。使用这种方法,我们只需定义一次 JVM 参数,并在两种执行模式下使用它们。
Note
需要注意的是,这种重用也适用于使用 Gradle 定义的 distZip
和 distTar
任务构建的应用程序分发。 application
插件,以及 Spring Boot Gradle 的 bootDistZip
和 bootDistTar
。
我们可以通过启动我们的自执行 JAR 而不是默认情况下 distTar
任务生成的 TAR 文件的内容来修改构建以创建 Docker 映像。为此,我们需要使用以下代码更改我们的 distDocker
配置块:
这将使我们的 distDocker
任务将可执行 jar 放入 Docker 映像而不是 TAR 存档中。
在前面的几个秘籍中,我们研究了如何以各种方式打包我们的应用程序以及如何部署它。下一个合乎逻辑的步骤是需要配置应用程序以提供一些行为控制以及一些特定于环境的配置值,这些值可能而且很可能会因环境而异。
这种 environmental 配置的常见示例 difference 是数据库设置。我们当然不想通过在我们的开发机器上运行的应用程序连接到生产环境数据库。在某些情况下,我们希望应用程序以不同的模式运行或使用不同的配置文件集,正如 Spring 所指的那样。例如,在实时或模拟器模式下运行 application。
对于这个秘籍,我们将从代码库的先前状态开始,添加对不同配置文件的支持,并检查如何将属性值用作其他属性中的占位符。
- We will start by adding an
@Profile
annotation to the@Bean
creation ofschedulerRunner
by changing the definition of theschedulerRunner(...)
method inBookPubApplication.java
, located in thesrc/main/java/org/test/bookpub
directory at the root of our project, to the following content:
- Start the application by running
./gradlew clean bootRun
. - Once the application is running, we should no longer see the previous log output from the
StartupRunner
class, which looked like this:
- Now, let's build the application by running
./gradlew clean bootJar
and start it by running./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger
; we will see the log output line show up again. - Another functionality that is enabled by the profile selector is the ability to add profile-specific property files. Let's create an
application-inmemorydb.properties
file in thesrc/main/resources
directory at the root of our project with the following content:
- Let's build the application by running
./gradlew clean bootJar
and start it by running./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger,inmemorydb
, which will use theinmemorydb
profile configuration in order to use the in-memory database instead of the file-based one.
在这个秘籍中,我们实验使用配置文件并根据活动配置文件应用额外的配置设置。 Profiles 最初是在 Spring Framework 3.2 中引入的,用于在上下文中有条件地配置 bean,具体取决于哪些配置文件处于活动状态。在 Spring Boot 中,此功能被进一步扩展以允许配置分离。
通过在我们的 StartupRunner@Bean
创建方法上放置一个 @Profile("logger")
注解,Spring 将被指示创建仅当记录器配置文件已被激活时 bean。通常,这是通过在应用程序启动期间在命令行中传递 --spring.profiles.active
选项来完成的。在测试中,另一种方法是在 Test
类上使用 @ActiveProfiles("profile")
注释,但不支持执行普通应用程序。也可以否定配置文件,例如 @Profile("!production")
。当使用这样的注释时(用 !
标记否定),只有在没有配置文件生产处于活动状态时才会创建 bean。
在启动期间,Spring Boot 将通过命令行传递的所有选项视为应用程序属性,因此在启动期间传递的任何内容最终都将作为能够使用的属性值。这种相同的机制不仅适用于新属性,还可以用作覆盖现有属性的一种方式。假设我们已经在 application.properties
文件中定义了一个活动配置文件,如下所示: spring.profiles.active=basic
。通过命令行传递
--spring.profiles.active=logger
选项,我们将从 basic
替换活动配置文件到 logger
。如果我们想包含一些配置文件而不考虑活动配置,Spring Boot 为我们提供了一个 spring.profiles.include
选项来配置。以这种方式设置的任何配置文件都将添加到活动配置文件列表中。
由于这些选项只不过是 regular Spring Boot 应用程序属性,因此它们都遵循相同的覆盖优先级层次结构。选项概述如下:
- Command-line arguments: These values supersede every other property source in the list, and you can always rest assured that anything passed via
--property.name=value
will take precedence over the other means. - JNDI attributes: They are the next in precedence priority. If you are using an application container that provides data via a JNDI
java:comp/env
namespace, these values will override all the other settings from below. - Java system properties: These values are another way to pass the properties to the application either via the
-Dproperty=name
command-line arguments or by callingSystem.setProperty(...)
in the code. They provide another way to replace the existing properties. Anything coming fromSystem.getProperty(...)
will win over the others in the list. - OS environment variables: Whether from Windows, Linux, OS X, or any other, they are a common way to specify a configuration, especially for locations and values. The most notable one is
JAVA_HOME
, which is a common way to indicate where the JVM location resides in the filesystem. If neither of the preceding settings are present, theENV
variables will be used for the property values instead of the ones mentioned as follows:
Note
由于操作系统环境变量通常不支持点 (.
) 或破折号 (-
),因此 Spring Boot 提供了自动重新映射在属性评估期间将下划线 (_
) 替换为点 (.
) 的机制;它还处理大小写转换。因此,JAVA_HOME
成为java.home
的同义词。
random.*
: This provides special support for the random values of primitive types that can be used as placeholders in configuration properties. For example, we can define a property namedsome.number=${random.int}
where${random.int}
will be replaced by some random integer value. The same goes for${random.value}
for textual values and${random.long}
for longs.application-{profile}.properties
: They are the profile-specific files that get applied only if a corresponding profile gets activated.application.properties
: They are the main property files that contain the base/default application configuration. Similar to the profile-specific ones, these values can be loaded from the following list of locations, with the top one taking priority over the lower entries:file:config/
: This is a/config
directory located in the current directory:file:
: This is the current directoryclasspath:/config
: This is a/config
package in the classpathclasspath:
: This is a root of the classpath
- @Configuration annotated classes annotated with @PropertySource: These are any in-code property sources that have been configured using annotations. We have seen an example of such usage the Adding custom connectors recipe from Chapter 22, Web Framework Behavior Tuning. They are very low in the precedence chain and are only preceded by the default properties.
- Default properties: They are configured via the
SpringApplication.setDefaultProperties(...)
call and are seldom used, as it feels very much like hardcoding values in code instead of externalizing them in configuration files.
如果企业已经使用特定的配置 系统,自定义编写或现成的,Spring Boot 为我们提供了通过 creationPropertySource 实现的“indexterm">。
假设我们有一个现有的配置设置,它使用流行的 Apache Commons 配置框架并将配置数据存储在 XML 文件中:
- To mimic our supposed pre-existing configuration system, add the following content to the dependencies section in the
build.gradle
file:
- Follow this up by creating a simple configuration file named
commons-config.xml
in thesrc/main/resources
directory at the root of our project with the following content:
- Next, we will create the
PropertySource
implementation file namedApacheCommonsConfigurationPropertySource.java
in thesrc/main/java/org/test/bookpub
directory at the root of our project with the following content:
- We will now create the
EnvironmentPostProcessor
implementation class so as to bootstrap ourPropertySource
namedApacheCommonsConfigurationEnvironmentPostProcessor.java
in thesrc/main/java/org/test/bookpub
directory at the root of our project with the following content:
- Finally, we will need to create a new directory named
META-INF
in thesrc/main/resources
directory at the root of our project and create a file namedspring.factories
in it with the following content:
- With the setup done, we are now ready to use our new properties in our application. Let's change the configuration of the
@Scheduled
annotation for ourStartupRunner
class located in thesrc/main/java/org/test/bookpub
directory at the root of our project, as follows:
- Let's build the application by running
./gradlew clean bootJar
and start it by running./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger
in order to ensure that ourStartupRunner
class is still logging the book count every ten seconds, as expected.
在这个秘籍中,我们探索了如何添加我们自己的自定义PropertySource
这使我们能够在 Spring Boot 环境中桥接现有系统。让我们来看看 inner 各个部分如何组合在一起的工作原理。
在上一节中,我们了解了不同的配置定义是如何叠加的,以及使用什么规则将它们叠加在一起。这将帮助我们更好地理解使用自定义 PropertySource
实现的 Apache Commons Configuration 的桥接是如何工作的。 (这不应与 @PropertySource
注释混淆!)
在 第 23 章中,编写自定义 Spring Boot Starters< /em>,我们了解了关于使用spring.factories< /code>,因此我们已经知道该文件用于定义 Spring Boot 在应用程序启动期间应自动合并的类。这次唯一的区别是,我们将配置
SpringApplicationRunListener
设置,而不是配置 EnableAutoConfiguration
设置。
我们创建了以下两个类来支持我们的需求:
ApacheCommonsConfigurationPropertySource
: This is the extension of theEnumerablePropertySource
base class that provides you with internal functionality in order to bridge XMLConfiguration from Apache Commons Configuration to the world of Spring Boot by providing transformation to get the specific property values by name via thegetProperty(String name)
implementation, and the list of all the supported property names via thegetPropertyNames()
implementation. In situations where you are dealing with the use case when the complete list of the available property names is not known or is very expensive to compute, you can just extend thePropertySource
abstract class instead of usingEnumerablePropertySource
.ApacheCommonsConfigurationEnvironmentPostProcessor
: This is the implementation of theEnvironmentPostProcessor
interface that gets instantiated by Spring Boot during the application startup and receives notification callback after the initial environment initialization has been completed, but before the application context startup. This class is configured inspring.factories
and is automatically created by Spring Boot.
在我们的后处理器中,我们实现了 postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application)
方法,它使我们能够访问 ConfigurableEnvironment
实例。在调用此回调时,我们将获得一个环境实例,该实例已经填充了前面层次结构中的所有属性。但是,我们将有机会在列表中的任何位置注入我们自己的 PropertySource
实现,我们将在 ApacheCommonsConfigurationPropertySource.addToEnvironment(. ..)
方法。
在我们的例子中,我们将选择在 systemEnvironment
下面按优先级顺序插入我们的源代码,但如果需要,我们可以将此顺序更改为我们想要的任何最高优先级。请注意不要将其设置得太高,以至于无法通过命令行参数、系统属性或环境变量覆盖您的属性。
前面的秘籍向我们介绍了 application 属性以及它们的配置方式。正如本章开头提到的,在应用程序部署过程中,几乎不可避免地会有一些依赖于环境的属性值。它们可以是数据库配置、服务拓扑,甚至是简单的功能配置,其中可能会在开发中启用某些东西,但还没有为生产做好准备。
在这个秘籍中,我们将学习如何使用外部驻留的 properties 文件进行环境特定的配置,该配置可能驻留在本地文件系统或互联网上的野外。
在这个秘籍中,我们将使用与上一秘籍中使用的所有现有配置相同的应用程序。我们将使用它来尝试使用位于本地文件系统中的外部配置属性以及来自 Internet URL(例如 GitHub 或任何其他)的启动。
- Let's start by adding a bit of code to log the value of our particular configuration property so that we can easily see the change in it as we do different things. Add an
@Bean
method to theBookPubApplication
class located in thesrc/main/java/org/test/bookpub
directory at the root of our project with the following content:
- Let's build the application by running
./gradlew clean bootJar
and start it by running./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger
so as to see the following log output:
- The value is empty, as we expected. Next, we will create a file named
external.properties
in our home directly with the following content:
- Let's run our application by executing
./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger --spring.config.location=file:/home/<username>/external.properties
in order to see the following output in the logs:
- We can also load the file as an HTTP resource and not from the local filesystem. So, place a file named
external.properties
with the content ofmy.config.value=From HTTP Config
somewhere on the web. It can even be checked in a GitHub or BitBucket repository, as long as it is accessible without any need for authentication. - Let's run our application by executing
./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger --spring.config.location=http://<your file location path>/external.properties
in order to see the following output in the logs:
在深入研究 external 配置设置的详细信息之前,让我们快速查看为打印属性而添加的代码日志中的值。焦点元素是 @Value
注解,可用于类字段或方法参数;它还指示 Spring 使用注释中定义的值自动注入带注释的变量。如果值位于以美元符号为前缀的环绕花括号中,(${ }
),Spring 将用相应应用程序 property< 中的值替换它/span> 或使用默认值,如果提供,在冒号后添加文本数据 (:< /代码>)。
在我们的例子中,我们将它定义为 @Value("${my.config.value:}")String configValue
,所以除非应用程序属性名为 my.config.value
存在,空字符串的默认值将分配给 configValue
方法参数。这种构造非常方便,无需显式连接环境对象的实例以从中获取特定的属性值,并且在测试期间简化了代码,需要模拟的对象更少。
能够指定应用程序 properties 配置文件的位置的支持旨在支持动态的大量 环境 拓扑,尤其是在云环境中。当编译后的应用程序被捆绑到不同的云镜像中时,通常会出现这种情况,这些镜像用于不同的环境,并由 Packer、Vagrant 等部署工具专门组装。
在这种情况下,在制作镜像时将配置文件放入镜像文件系统是很常见的,具体取决于它的目标环境。 Spring Boot 提供了一种非常方便的功能,可以通过命令行参数指定应该添加到应用程序配置包中的配置属性文件所在的位置。
使用 --spring.config.location
启动选项,我们可以指定一个或多个文件的位置,然后可以用逗号分隔(,
) 添加到默认值。文件名称可以是来自本地文件系统、类路径或远程 URL 的文件。这些位置将由 DefaultResourceLoader
类解析,或者,如果通过 SpringApplication
构造函数或设置器进行配置,则由实现由 SpringApplication
实例提供。
如果位置包含目录,则名称应以 /
结尾,以便让 Spring Boot 知道它应该查找 application.properties< /code> 文件在这些目录中。
如果您想更改文件的默认名称,Spring Boot 也为您提供了此功能。只需将 --spring.config.name
选项设置为您想要的任何文件名。
在前面的秘籍中,我们有 number 次,暗示 Spring Boot 应用程序的配置值可以使用 OS 环境变量传递和覆盖。操作系统依靠这些变量来存储有关各种事物的信息。我们可能需要设置几次 JAVA_HOME
或 PATH
,这些都是环境变量的例子。如果使用 Heroku 或 Amazon AWS 等 PaaS 系统部署应用程序,操作系统环境变量也是一项非常重要的功能。在这些环境中,数据库访问凭证和各种 API 令牌等配置值都通过环境变量提供。
它们的强大之处在于能够完全外部化简单键值数据对的配置,而无需依赖将属性或其他文件放置在特定位置,并将其硬编码在应用程序代码库中。这些变量对于特定的操作系统也是不可知的,并且可以以相同的方式在 Java 程序中使用,System.getenv()
,无论程序在哪个操作系统上运行.
在这个秘籍中,我们将探索如何利用这种能力将配置属性传递给我们的 Spring Boot 应用程序。我们将继续使用上一个秘籍中的代码库,并尝试几种不同的方式来启动应用程序和使用操作系统环境变量来更改某些属性的配置值。
- In the previous recipe, we added a configuration property named
my.config.value
. Let's build the application by running./gradlew clean bootJar
and start it by runningMY_CONFIG_VALUE="From ENV Config" ./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.profiles.active=logger
so as to see the following output in the logs:
- If we want to use the environment variables while running our application via the Gradle
bootRun
task, the command line will beMY_CONFIG_VALUE="From ENV Config" ./gradlew clean bootRun
and should produce the same output as in the preceding step. - Conveniently enough, we can even mix and match how we set the configurations. We can use the environment variable to configure the
spring.config.location
property and use it to load other property values from the external properties file, as we did in the previous recipe. Let's try this by launching our application by executingSPRING_CONFIG_LOCATION= file:/home/<username>/external.properties ./gradlew bootRun
. We should see the following in the logs:
Note
虽然使用环境变量非常方便,但如果这些变量的数量太多,它确实会产生维护开销。为帮助解决此问题,最好使用委托方法,通过设置SPRING_CONFIG_LOCATION
变量来配置特定于环境的属性文件的位置,通常是通过加载它们从 URL 位置。
正如您从环境配置层次结构部分中了解到的,Spring Boot 提供了多种提供配置属性的方法。其中每一个都通过适当的 PropertySource
实现进行管理。在实现 ApacheCommonsConfigurationPropertySource
时,我们研究了如何创建 PropertySource
的自定义实现。 Spring Boot 已经提供了一个 SystemEnvironmentPropertySource
实现供我们开箱即用。这甚至会自动注册到环境接口的默认实现:SystemEnvironment
。
由于 SystemEnvironment
实现 provides 在众多不同的 PropertySource
实现,覆盖是无缝进行的,仅仅是因为 SystemEnvironmentPropertySource
类在列表中比 application.properties
文件一。
您应该注意的一个重要方面是使用带有下划线 (_
) 的 ALL_CAPS
来分隔单词而不是传统的传统 all.lower.cased
格式,用点 (.
) 分隔 Spring Boot 中用于命名配置属性的单词。这是由于某些操作系统的特性,即 Linux 和 OS X,它们禁止在名称中使用点 (.
),而是鼓励使用 ALL_CAPS
下划线分隔符号。
在不需要使用环境变量来指定或覆盖配置属性的情况下,Spring 为我们提供了 -Dspring.getenv.ignore
系统属性,可以设置为true 并防止使用环境变量。如果由于在某些应用程序服务器上运行代码或可能不允许访问环境变量的特定安全策略配置而在日志中看到错误或异常,您可能希望将此设置更改为 true。
虽然 environment 变量在极少数情况下可能会受到影响,但始终可以信任良好的旧 Java 系统属性有你。除了使用以双破折号 (--
) 为前缀的属性名称表示的环境变量和命令行参数之外,Spring Boot 提供<一个 id="id325891479" class="indexterm"> 您能够使用普通的 Java 系统属性来设置或覆盖配置属性。
这在许多情况下很有用,特别是如果您的应用程序在容器中运行,该容器在启动期间通过您想要访问的系统属性设置某些值,或者如果属性值未通过命令行设置-D
参数,而是在某些库中通过代码和调用 System.setProperty(...)
,特别是如果属性正在从内部访问值 一种静态方法。虽然可以说这些情况很少见,但只需要一个让您向后弯腰努力尝试将此值集成到您的应用程序中。
在这个秘籍中,我们将使用与前一个相同的应用程序可执行文件,唯一的区别是我们使用 Java 系统属性而不是命令行参数或环境变量来在运行时设置我们的配置属性。
- Let's continue our experiments by setting the
my.config.value
configuration property. Build the application by running./gradlew clean bootJar
and start it by runningjava -Dmy.config.value="From System Config" -jar ./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar
so as to see the following in the logs:
- If we want to be able to set the Java system property while running our application using the Gradle's
bootRun
task, we will need to add this to theapplicationDefaultJvmArgs
configuration in thebuild.gradle
file. Let's add-Dmy.config.value=Gradle
to this list and start the application by running./gradlew clean bootRun
. We should see the following in the logs:
- As we made the
applicationDefaultJvmArgs
setting to be shared withlaunch.script
, rebuilding the application by running./gradlew clean bootJar
and starting it by running./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar
should yield the same output in the logs as in the preceding step.
您可能已经猜到,Java 系统属性被用于环境变量的类似 机制所使用,您会正确的。唯一真正的区别是 PropertySource
的实现。这一次,StandardEnvironment
使用了更通用的 MapPropertySource
实现。
您可能还注意到需要使用 java -Dmy.config.value="From System Config" -jar ./build/libs/bookpub-0.0.1- 启动我们的应用程序SNAPSHOT-exec.jar
命令,而不仅仅是简单地调用自执行打包的 JAR。这是因为,与环境变量和命令行参数不同,Java 系统属性必须在 Java 可执行文件上设置在其他所有内容之前。
我们确实设法通过有效地硬编码 build.gradle
文件中的值来解决这一需求,该文件与我们对 所做的增强相结合launch.script
,允许我们将 my.config.value
属性嵌入到自执行 jar 的命令行中,以及与 Gradle 的 < code class="literal">bootRun 任务。
将这种 方法 与配置属性一起使用的风险在于,它总是会覆盖我们在更高层设置的值配置,例如 application.properties
等。除非您明确构建 Java 可执行命令行并且不使用打包 JAR 的自启动功能,否则最好不要使用 Java 系统属性并考虑使用命令行参数或环境变量。
我们已经研究了许多不同的方法来外部添加或覆盖特定属性的值,或者使用环境 变量、系统属性或命令行参数。所有这些选项都为我们提供了很大的灵活性,但除了外部属性文件之外,都仅限于一次设置一个属性。在使用属性文件时,该语法在表示嵌套的分层数据结构方面并不是最好的,并且可能会有些棘手。为了避免这种情况,Spring Boot 为我们提供了在外部传递 JSON 编码内容的能力,该内容包含整个配置层次结构的设置。
在这个秘籍中,我们将使用与前一个相同的应用程序可执行文件,唯一的区别是使用外部 JSON 内容在运行时设置我们的配置属性。
- Let's continue our experiments by setting the
my.config.value
configuration property. Build the application by running./gradlew clean bootJar
and start it by runningjava -jar ./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar --spring.application.json={"my":{"config":{"value":"From external JSON"}}}
so as to see the following in the logs:
- If we want to be able to set the content using Java system properties, we can use
-Dspring.application.json
instead, assigning the same JSON content as the value.
- Alternatively, we can also rely on the
SPRING_APPLICATION_JSON
environment variable to pass the same JSON content in the following way:
就像我们看到的所有其他配置方法一样,JSON 内容由专用的 EnvironmentPostProcessor
实现使用。唯一的区别是将 JSON 树扁平化为扁平属性映射,以匹配点分隔的属性命名样式。在我们的例子中, my->config->value
嵌套映射被转换为只有一个键 my.config 的平面映射.value
,其值为 From external JSON
。
JSON 内容的设置可以来自任何属性源,在加载时可从环境中获得,其中包含一个名为 spring.application.json
的键,其值为 valid JSON 内容,并不仅限于由环境变量设置或使用 SPRING_APPLICATION_JSON
名称或 Java 系统属性。
此功能对于批量提供外部定义的、特定于环境的配置非常有用。最好的方法是使用 Chef、Puppet、Ansible、Packer 等机器/图像配置工具在机器实例上设置 SPRING_APPLICATION_JSON
环境变量。这使您能够将整个配置层次结构存储在外部的一个 JSON 文件中,然后只需在特定机器上配置正确的内容在配置期间,只需设置一个环境变量。该机器上运行的所有应用程序将在启动时自动使用它。
到目前为止,我们对 configuration 所做的一切都与本地数据集相关联。在真实的大型企业环境中,情况并非总是如此,并且经常希望能够在数百甚至数千个实例或机器上进行大规模的配置更改。
有许多工具可以帮助你完成这项任务,在这个秘籍中,我们将看看一个在我看来从小组中脱颖而出的工具,它让你能够干净而优雅地配置环境使用分布式数据存储的启动应用程序的变量。该工具的名称是 Consul。它是 Hashicorp 的开源产品,旨在发现和配置大型分布式基础架构中的服务。
在这个秘籍中,我们将看看如何安装和配置 Consul,并试验它提供的一些关键功能。这将为我们的下一个秘籍提供必要的熟悉度,我们将使用 Consul 提供启动应用程序所需的配置值。
- Go to https://consul.io/downloads.html and download the appropriate archive, depending on the operating system that you are using. Consul supports Windows, OS X, and Linux, so it should work for the majority of readers.
Note
如果您是 OS X 用户,您可以使用 Homebrew 安装 Consul,方法是运行brew install caskroom/cask/brew-cask
,然后运行brew cask install领事
。
- After the installation, we should be able to run
consul --version
and see the following output:
- With Consul successfully installed, we should be able to start it by running the
consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul
command and our terminal window will display the following:
- While Consul can also provide discovery for services, health checks, distributed locks, and more, we are going to focus on the key/value service as this is what will be used to provide the configuration in the next recipe. So, let's put the
From Consul Config
value in the key/value store by executing thecurl -X PUT -d 'From Consul Config' http://localhost:8500/v1/kv/bookpub/my/config/value
command.
Note
如果您使用的是 Windows,您可以从http://curl.haxx.se 获取curl /download.html。
- We can also retrieve the data by running the
curl http://localhost:8500/v1/kv/bookpub/my/config/value
command and should see the following output:
- We can delete this value by running the
curl -X DELETE http://localhost:8500/v1/kv/bookpub/my/config/value
command. - In order to modify the existing value and change it for something else, execute the
curl -X PUT -d 'newval' http://localhost:8500/v1/kv/bookpub/my/config/value?cas=20
command.
关于 Consul 的工作原理及其键/值服务的所有可能选项的详细说明需要一本书,因此我们将只看基本部分。强烈建议您在 https:/ 阅读 Consul 的文档/consul.io/intro/getting-started/services.html。
在第3步中,我们启动了Consul 服务器模式下的代理。它充当主主节点,在实际部署中,运行在各个实例上的本地代理将使用服务器节点连接并从中检索数据。为了我们的测试目的,我们将只使用这个服务器节点,就好像它是一个本地代理一样。
启动时显示的信息显示,我们的节点已作为服务器节点启动,在端口 8500
上建立 HTTP 服务以及 DNS 和 RPC 服务,如果是这样选择的话连接到它。 We can also see that there is only one node in the cluster, ours, and we are the elected leader running in a healthy state.
由于我们将通过 cURL 使用方便的 RESTful HTTP API,我们所有的请求都将使用端口 8500
上的 localhost。作为一个 RESTful API,它完全遵循 CRUD 动词术语,为了插入数据,我们将在 /v1/ 上使用
端点以设置 PUT
方法kvbookpub/my/config/value
键。
检索数据更加直接:我们只需使用所需的密钥向同一个 /v1/kv
服务发出 GET
请求. DELETE
也是如此,唯一的区别是方法名称。
更新操作需要 URL 中的更多信息,即 cas
参数。该参数的值应该是所需键的ModifyIndex
,可以从GET
请求中获取。在我们的例子中,它的值为 20。
在前面的秘籍中,我们安装了 Consul 服务并试验了它的键/值功能,以了解我们如何操作其中的数据,以便将 Consul 与我们的应用程序集成,并使数据提取过程无缝且无创地从应用角度。
因为我们不希望我们的应用程序 知道任何关于 Consul 并且必须明确<一个 id="id325954115" class="indexterm"> 连接到它,即使存在这种可能性,我们将使用另一个实用程序,它也是由 Hashicorp 作为开源创建的,称为 envconsul。它将为我们连接到 Consul 服务,提取指定的 configuration 键/值树,并 expose 它作为环境变量在启动我们的应用程序时使用。很酷,对吧?
在我们开始启动我们在前面的秘籍中创建的应用程序之前,我们需要安装 envconsul 实用程序。
从 https://github.com/ 下载适用于您各自操作系统的二进制文件hashcorp/envconsul/releases 并将可执行文件解压缩到您选择的任何目录,但最好将其放在 PATH 中的某个位置。
从下载的存档中提取 envconsul 后,我们就可以开始使用它来配置我们的应用程序了。
- If you have not already added the value for the
my/config/value
key to Consul, let's add it by runningcurl -X PUT -d 'From Consul Config' http://localhost:8500/v1/kv/bookpub/my/config/value
. - The first step is to make sure envconsul can connect to the Consul server and that it extracts the correct data based on our configuration key. Let's execute a simple test by running the
envconsul --once --sanitize --upcase --prefix bookpub env
command. We should see the following in the output:
- After we have verified that envconsul is returning the correct data to us, we will use it to launch our
BookPub
application by runningenvconsul --once --sanitize --upcase --prefix bookpub ./gradlew clean bootRun
. Once the application has started, we should see the following output in the logs:
- We can do the same thing by building the self-starting executable JAR by running
./gradlew clean bootJar
, and start it by runningenvconsul --once --sanitize --upcase --prefix bookpub ./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar
to make sure we see the same output in the logs as in the preceding step. If you seeGradle
instead ofFrom Consul Config
, make sure theapplicationDefaultJvmArgs
configuration inbuild.gradle
does not have-Dmy.config.value=Gradle
in it. - Another marvelous ability of envconsul is not only to export the configuration key values as environment variables, but also to monitor for any changes and restart the application if the values in Consul change. Let's launch our application by running
envconsul --sanitize --upcase --prefix bookpub ./build/libs/bookpub-0.0.1-SNAPSHOT-exec.jar
, and we should see the following value in the log:
- We will now use the consul command to get the current
ModifyIndex
of our key and update its value toFrom UpdatedConsul Config
by opening another terminal window and executingcurl http://localhost:8500/v1/kv/bookpub/my/config/value
, grabbing theModifyIndex
value, and using it to executecurl -X PUT -d 'From UpdatedConsul Config' http://localhost:8500/v1/kv/bookpub/my/config/value?cas=<ModifyIndex Value>
. We should see our running application magically restart itself and our newly updated value displayed in the log at the end:
我们刚刚做的很甜蜜,对吧?让我们更详细地研究幕后发生的魔法。我们将首先剖析命令行并解释每个参数控制选项的作用。
我们的第一个执行命令行是 envconsul --once --sanitize --upcase --prefix bookpub ./gradlew clean bootRun
,所以让我们看看我们到底做了什么,如如下:
- First, one might notice that there is no indication about which Consul node we should be connecting to. This is because there is an implicit understanding or an assumption that you already have a Consul agent running locally on
localhost:8500
. If this is not the case for whatever reason, you can always explicitly specify the Consul instance to connect via the--consul localhost:8500
argument added to the command line. - The
--prefix
option specifies the starting configuration key segment in which to look for the different values. When we were adding keys to Consul, we used the following key:bookpub/my/config/value
. By specifying the--prefix bookpub
option, we tell envconsul to strip thebookpub
part of the key and use all the internal tree elements inbookpub
to construct the environment variables. Thus,my/config/value
becomes the environment variable. - The
--sanitize
option tells envconsul to replace all the invalid characters with underscores (_
). So, if we were to only use--sanitize
, we would end up withmy_config_value
as an environment variable. - The
--upcase
option, as you might already have guessed, changes the environment variable key to all upper case characters, so when combined with the--sanitize
option,my/config/value
key gets transformed into theMY_CONFIG_VALUE
environment variable. - The
--once
option indicates that we only want to externalize the keys as environment variables once and do not want to continuously monitor for changes in the Consul cluster. If a key in our prefix tree has changed its value, we re-externalize the keys as environment variables and restart the application.
最后一个选项 --once
提供了非常有用的功能选择。如果您只对通过使用 Consul 共享配置的应用程序的初始引导感兴趣,那么键将被设置为环境变量,应用程序将被启动,并且 envconsul 将认为它的工作已完成。但是,如果您想监视 Consul 集群中键/值的更改,并且在更改发生后,重新启动您的应用程序以反映新更改,然后删除 --once
选项和 envconsul 将在更改发生后重新启动应用程序。
这种行为对于数据库连接配置的近乎即时的更改非常有用和方便。想象一下,您需要从一个数据库快速故障转移到另一个数据库,并且您的 JDBC URL 是通过 Consul 配置的。您需要做的就是推送一个新的 JDBC URL 值,envconsul 几乎会立即检测到此更改并重新启动应用程序,告诉它连接到一个新的数据库节点。
目前,这个功能是通过发送一个传统的SIGTERM信号给一个应用程序运行进程来实现的,告诉它终止,一旦进程退出,重新启动应用程序。这可能并不总是理想的行为,特别是如果应用程序需要一些时间才能启动并能够占用流量。您不希望关闭整个 Web 应用程序集群,即使只是几分钟。
为了更好地处理这种情况,envconsul 得到了增强,能够发送许多可以配置的标准信号通过新添加的 --kill-signal
选项。使用此选项,我们可以指定使用任何 SIGHUP、SIGTERM、SIGINT、SIGQUIT、SIGUSR1 或 SIGUSR2 信号来代替默认的 SIGTERM,一旦检测到键/值更改,就将其发送到正在运行的应用程序进程。
由于大多数行为都非常特定于特定操作系统和在其上运行的 JVM,Java 中的进程信号处理并不那么清晰和直接。列表中的某些信号无论如何都会终止应用程序,或者在 SIGQUIT 的情况下,JVM 会将 Core Dump 打印到标准输出中。但是,有一些方法可以配置 JVM,具体取决于操作系统,让我们使用 SIGUSR1 和 SIGUSR2 而不是对这些信号本身进行操作,但不幸的是,该主题超出了本书的范围。
以下是如何 处理信号处理程序: https://github.com/spotify/daemon-java< /a>,或在 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/signals.html了解详细说明。