章节 IV. Spring Boot 特性

这一部分深入研究了解 Spring boot 的细节。在这里你可以了解你可能想要使用和自定义的关键特性。如果你还没有准备好这样做,你可能需要阅读 章节 II, “新手入门” 和 章节 III, “使用 Spring Boot” 部分,这样你就有基础部分的良好基础。

23. SpringApplication

SpringApplication 类提供一种从 main() 方法引导 Spring 应用程序启动的便捷方法。在大多数情况下,你可以委托给静态 SpringApplication.run 方法,如下面的示例所示:

public static void main(String[] args) {
	SpringApplication.run(MySpringConfiguration.class, args);
}

当你的应用程序启动时,你应该可以看到类似下面输出的一些内容:

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   v2.1.0.BUILD-SNAPSHOT

2013-07-31 00:08:16.117  INFO 56603 --- [           main] o.s.b.s.app.SampleApplication            : Starting SampleApplication v0.1.0 on mycomputer with PID 56603 (/apps/myapp.jar started by pwebb)
2013-07-31 00:08:16.166  INFO 56603 --- [           main] ationConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6e5a8246: startup date [Wed Jul 31 00:08:16 PDT 2013]; root of context hierarchy
2014-03-04 13:09:54.912  INFO 41370 --- [           main] .t.TomcatServletWebServerFactory : Server initialized with port: 8080
2014-03-04 13:09:56.501  INFO 41370 --- [           main] o.s.b.s.app.SampleApplication            : Started SampleApplication in 2.992 seconds (JVM running for 3.658)

默认情况下,显示了 INFO 日志消息,包括一些相关的启动细节,例如启动应用程序的用户。如果你需要除 INFO 以外的日志级别,你可以设置它,如 小节 26.4, “日志级别” 描述的那样。

23.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's listening on port 8080 or configure this application to listen on another port.
[Note] Note

Spring Boot 提供了许多 FailureAnalyzer 实现,你也可以添加你自己的。

如果没有失败分析器能够处理异常,你仍可以显示完整的条件报告,以便更好地理解错误所在。为此,你需要为 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener 启用 debug 属性或启用 DEBUG 日志。

例如,如果你使用 java -jar 来运行你的应用程序,你可以像下面这样启用 debug 属性:

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

23.2 定制横幅

在启动时打印的横幅可以通过在你的类路径中添加一个 banner.txt 或通过设置 spring.banner.location 为该文件的路径来修改。如果文件有 UTF-8 之外的编码,你可以设置 spring.banner.charset。除了文本文件之外,你也可在你的类路径中添加一个 banner.gif、banner.jpg 或 banner.png 图片文件,或者设置 spring.banner.image.location 属性。图像被转换成 ASCII 艺术表现,并打印在任何文本横幅之上。

在你的 banner.txt 文件中,你可以使用一些下面的占位符:

表格 23.1. Banner 变量

变量 描述

${application.version}

你的应用程序的版本号,如 MANIFEST.MF 中所声明的。例如,Implementation-Version: 1.0 被打印成 1.0。

${application.formatted-version}

你的应用程序的版本号,如 MANIFEST.MF 中所声明的,并格式化显示(用括号括起来,加 v 前缀)。例如 (v1.0)。

${spring-boot.version}

你使用的 Spring Boot 版本,例如 2.1.0.BUILD-SNAPSHOT。

${spring-boot.formatted-version}

你使用的 Spring Boot 版本,格式化显示(用括号括起来,加 v 前缀)。例如 (v2.1.0.BUILD-SNAPSHOT)。

${Ansi.NAME} (或 ${AnsiColor.NAME}、${AnsiBackground.NAME}、${AnsiStyle.NAME})

NAME 是 ANSI 转译代码的名称。更多细节参见 AnsiPropertySource。

${application.title}

你的应用程序的标题,如 MANIFEST.MF 中所声明的。例如,Implementation-Title: MyApp 被打印成 MyApp。


[Tip] Tip

如果你要以编程的方式生成横幅,可以使用 SpringApplication.setBanner(…​) 方法。使用 org.springframework.boot.Banner 接口并实现你自己的 printBanner() 方法。

你还可以使用 spring.main.banner-mode 属性来确定横幅是否必须打印在 System.out(console)上,发送到配置的日志器(log),或者根本不生成(off)。

打印横幅被注册为一个单独的 bean,名称如下:springBootBanner。

[Note] Note

YAML映射 off 为 false,所以,如果你想禁用应用程序中的横幅,请务必添加引号,如下面的示例所示:

spring:
	main:
		banner-mode: "off"

23.3 定制 SpringApplication

如果 SpringApplication 默认内容不符合你的口味,你可以创建本地实例并自定义它。例如,要关闭横幅,你可以写:

public static void main(String[] args) {
	SpringApplication app = new SpringApplication(MySpringConfiguration.class);
	app.setBannerMode(Banner.Mode.OFF);
	app.run(args);
}
[Note] Note

传递给 SpringApplication 的构造函数参数是 Spring bean 的配置源。大多数情况下,这些都是对 @Configuration 类的引用,但它们也可以是 XML 配置的引用或应该被扫描的包。

也可以通过使用 application.properties 文件来配置 SpringApplication。更多详细信息请参阅 章节 24, 外部配置

有关配置选项的完整列表,请参阅 SpringApplication Javadoc。

23.4 Fluent 构造器 API

如果你需要构建一个 ApplicationContext 层次结构(具有父/子关系的多个上下文),或许你更喜欢使用 “fluent” 构造器 API,你可以使用 SpringApplicationBuilder。

SpringApplicationBuilder 允许你将多个方法调用链接在一起,并包含 parent 和 child 方法,它们可以创建层次结构,如下面的示例所示:

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

创建 ApplicationContext 层次结构时有一些限制。例如,Web 组件必须包含在子上下文中,并且父子上下文都要使用相同的 Environment。完整的详细信息请参阅 SpringApplicationBuilder。

23.5 应用程序事件和监听器

除了通常的 Spring Framework 事件之外,如 ContextRefreshedEvent,SpringApplication 还发送一些额外的应用程序事件。

[Note] Note

一些事件实际上是在 ApplicationContext 创建之前触发的,因此你不能使用 @Bean 来注册监听器。你可以使用 SpringApplication.addListeners(…​) 方法或 SpringApplicationBuilder.listeners(…​) 方法来注册它们。

如果你希望这些监听器自动注册,而不管应用程序是如何创建的,你可以在项目中添加一个 META-INF/spring.factories 文件,并使用 org.springframework.context.ApplicationListener 键来引用你的监听器,如下面的示例所示:

org.springframework.context.ApplicationListener=com.example.project.MyListener

应用程序事件按以下顺序发送,如果你的应用程序运行:

  1. 运行开始时,在任何处理之前发送 ApplicationStartingEvent,除了注册监听器和初始化器之外。
  2. 在上下文创建之前,上下文中使用的 Environment 是可知时发送 ApplicationEnvironmentPreparedEvent。
  3. 在刷新开始之前但在 bean 定义已加载之后发送 ApplicationPreparedEvent。
  4. 在已刷新上下文之后,但在调用任何应用程序和命令行运行器之前,发送 ApplicationStartedEvent 。
  5. 调用所有应用程序和命令行运行器之后发送 ApplicationReadyEvent。它指示应用程序已准备好服务请求。
  6. 如果启动时存在异常时发送 ApplicationFailedEvent。
[Tip] Tip

你通常不需要使用应用程序事件,但知道它们的存在是很方便的。在内部,Spring Boot 使用事件来处理各种任务。

应用程序事件是使用 Spring Framework 的事件发布机制发送的。这种机制的一部分确保子上下文中发布给监听器的事件也会发布到一些父上下文中的监听器。因此,如果你的应用程序使用 SpringApplication 实例的层次结构,那么监听器可以接收同一类型的应用程序事件的多个实例。

为了允许监听器区分其上下文的事件和派生上下文的事件,它应该请求注入其应用程序上下文,然后将注入的上下文与事件的上下文进行比较。上下文可以通过实现 ApplicationContextAware 来注入,如果监听器是一个 bean,则使用 @Autowired。

23.6 Web 环境

SpringApplication 试图为你创建正确的 ApplicationContext 类型。用于确认 WebApplicationType 的算法相当简单:

  • 如果目前是 Spring MVC,则使用 AnnotationConfigServletWebServerApplicationContext
  • 如果目前不是 Spring MVC 而是 Spring WebFlux,则使用 AnnotationConfigReactiveWebServerApplicationContext
  • 其它的,则使用 AnnotationConfigApplicationContext

这意味着,如果你在同一个应用程序中使用 Spring MVC 和 Spring WebFlux 的新 WebClient,默认情况下将使用 Spring MVC。你可以很容易地通过 setWebApplicationType(WebApplicationType) 来覆盖它。

还可以调用 setApplicationContextClass(…​) 来完全控制所使用的 ApplicationContext 类型。

[Tip] Tip

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

23.7 访问应用程序参数

如果你需要访问传递给 SpringApplication.run(…​) 的应用程序参数,你可以注入 org.springframework.boot.ApplicationArguments bean。ApplicationArguments 接口提供了对原始的 String[] 参数和 option 和 non-option 参数的访问,如下面的示例所示:

import org.springframework.boot.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;

@Component
public class MyBean {

	@Autowired
	public MyBean(ApplicationArguments args) {
		boolean debug = args.containsOption("debug");
		List<String> files = args.getNonOptionArgs();
		// if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
	}

}
[Tip] Tip

Spring Boot 还使用 Spring Environment 注册了 CommandLinePropertySource。这样,你也可以通过使用 @Value 注解来注入单个应用程序参数。

23.8 使用 ApplicationRunner 或 CommandLineRunner

如果你需要在 SpringApplication 启动之后运行一些特定的代码,则可以实现 ApplicationRunner 或 CommandLineRunner 接口。两个接口以相同的方式工作,并提供一个单独的 run 方法,这都是在 SpringApplication.run(…​) 完成之前调用。

CommandLineRunner 提供了一个简单的字符串数组形式的应用程序参数的访问,而 ApplicationRunner 则使用前面讨论的 ApplicationArguments 接口。下面的示例展示了一个带有 run 方法的 CommandLineRunner。

import org.springframework.boot.*;
import org.springframework.stereotype.*;

@Component
public class MyBean implements CommandLineRunner {

	public void run(String... args) {
		// Do something...
	}

}

如果定义了几个 CommandLineRunner 或 ApplicationRunner bean 必须按特定的顺序调用,你可以额外实现 org.springframework.core.Ordered 接口或使用 org.springframework.core.annotation.Order 注解。

23.9 退出应用

每个 SpringApplication 注册了一个 JVM 中的关闭钩子,以确保在退出时关闭 ApplicationContext。可以使用所有标准的 Spring 生命周期回调(如 DisposableBean 接口或 @PreDestroy 注解)。

此外,如果希望 SpringApplication.exit() 调用时,bean 会返回特定的退出代码,则它们可以实现 org.springframework.boot.ExitCodeGenerator 接口。这个退出代码可以传递给 System.exit() 以将它作为状态代码返回,如下面的示例所示:

@SpringBootApplication
public class ExitCodeApplication {

	@Bean
	public ExitCodeGenerator exitCodeGenerator() {
		return () -> 42;
	}

	public static void main(String[] args) {
		System.exit(SpringApplication
				.exit(SpringApplication.run(ExitCodeApplication.class, args)));
	}

}

此外,ExitCodeGenerator 接口可以通过异常来实现。当遇到这样的异常时,Spring Boot 返回由实现的 getExitCode() 方法提供的退出代码。

23.10 管理员特性

可以通过指定 spring.application.admin.enabled 属性来启用管理员相关的特性。这在 MBeanServer 平台上暴漏了 SpringApplicationAdminMXBean。你可以使用此特性远程管理 Spring Boot 应用程序。这个特性也可以用于所有服务封装器实现。

[Tip] Tip

如果你想知道应用程序运行在哪个 HTTP 端口上,可以通过 local.server.port 键来获得。

[Caution] Caution

启用此功能时要小心,因为 MBean 暴露了一个关闭应用程序的方法。