SpringApplication
SpringApplication 类提供了一种便捷的方式来引导从 main()
方法启动的 Spring 应用程序。在许多情况下,你可以委托给静态的 SpringApplication.run(Class, String…) 方法,如下例所示:
- Java
- Kotlin
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
当您的应用程序启动时,您应该会看到类似以下输出:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.2)
2025-01-23T11:42:49.219Z INFO 126366 --- [ main] o.s.b.d.f.logexample.MyApplication : Starting MyApplication using Java 17.0.14 with PID 126366 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2025-01-23T11:42:49.228Z INFO 126366 --- [ main] o.s.b.d.f.logexample.MyApplication : No active profile set, falling back to 1 default profile: "default"
2025-01-23T11:42:52.101Z INFO 126366 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-01-23T11:42:52.135Z INFO 126366 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-01-23T11:42:52.136Z INFO 126366 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.34]
2025-01-23T11:42:52.192Z INFO 126366 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-01-23T11:42:52.204Z INFO 126366 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2825 ms
2025-01-23T11:42:53.383Z INFO 126366 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-01-23T11:42:53.411Z INFO 126366 --- [ main] o.s.b.d.f.logexample.MyApplication : Started MyApplication in 5.862 seconds (process running for 6.981)
2025-01-23T11:42:53.423Z INFO 126366 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2025-01-23T11:42:53.441Z INFO 126366 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
默认情况下,会显示 INFO
级别的日志信息,包括一些相关的启动细节,例如启动应用程序的用户。如果你需要除 INFO
之外的日志级别,可以按照 日志级别 中的描述进行设置。应用程序版本是通过主应用程序类包的实现版本来确定的。启动信息日志可以通过将 spring.main.log-startup-info
设置为 false
来关闭。这也会关闭应用程序的活动配置文件的日志记录。
要在启动期间添加额外的日志记录,您可以在 SpringApplication 的子类中重写 logStartupInfo(boolean)
方法。
启动失败
如果你的应用程序启动失败,注册的 FailureAnalyzer Bean 将有机会提供专门的错误信息和具体的修复操作。例如,如果你在端口 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 实现,并且你可以添加自己的。
如果没有故障分析器能够处理该异常,你仍然可以显示完整的条件报告,以便更好地理解发生了什么问题。为此,你需要为 ConditionEvaluationReportLoggingListener 启用调试属性 或 启用 DEBUG 日志记录。
例如,如果您使用 java -jar
运行您的应用程序,可以按如下方式启用 debug
属性:
$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug
延迟初始化
SpringApplication 允许应用程序以懒加载的方式初始化。当启用懒加载初始化时,Bean 会在需要时才被创建,而不是在应用程序启动时创建。因此,启用懒加载初始化可以减少应用程序启动所需的时间。在 Web 应用程序中,启用懒加载初始化会导致许多与 Web 相关的 Bean 在接收到 HTTP 请求之前不会被初始化。
延迟初始化的一个缺点是,它可能会延迟发现应用程序中的问题。如果一个配置错误的 bean 被延迟初始化,那么在启动期间将不会发生故障,而问题只有在 bean 被初始化时才会显现出来。此外,还必须确保 JVM 有足够的内存来容纳应用程序的所有 bean,而不仅仅是在启动期间初始化的那些 bean。出于这些原因,延迟初始化默认情况下是关闭的,并且建议在启用延迟初始化之前对 JVM 的堆大小进行微调。
可以通过编程方式使用 SpringApplicationBuilder 上的 lazyInitialization
方法或 SpringApplication 上的 setLazyInitialization
方法来启用延迟初始化。或者,也可以使用 spring.main.lazy-initialization
属性来启用,如下例所示:
- Properties
- YAML
spring.main.lazy-initialization=true
spring:
main:
lazy-initialization: true
如果你想在应用程序的其余部分使用懒加载的同时,禁用某些 bean 的懒加载,你可以使用 @Lazy(false)
注解显式地将它们的 lazy
属性设置为 false
。
自定义横幅
启动时打印的横幅可以通过在类路径中添加 banner.txt
文件或通过将 spring.banner.location
属性设置为该文件的位置来更改。如果文件的编码不是 UTF-8,你可以设置 spring.banner.charset
。
在您的 banner.txt
文件中,您可以使用 Environment 中可用的任何键,以及以下任何占位符:
表 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 版本。例如 3.4.2 。 |
${spring-boot.formatted-version} | 你正在使用的 Spring Boot 版本,格式化显示(用括号包围并加上 v 前缀)。例如 (v3.4.2) 。 |
${Ansi.NAME} (或 ${AnsiColor.NAME} , ${AnsiBackground.NAME} , ${AnsiStyle.NAME} ) | 其中 NAME 是 ANSI 转义码的名称。详情请参见 AnsiPropertySource。 |
${application.title} | 应用程序的标题,在 MANIFEST.MF 中声明。例如 Implementation-Title: MyApp 将输出为 MyApp 。 |
如果你想以编程方式生成横幅,可以使用 SpringApplication.setBanner(…)
方法。使用 Banner 接口并实现你自己的 printBanner()
方法。
你也可以使用 spring.main.banner-mode
属性来决定是否需要在 System.out 上打印横幅(console
),或者将其发送到配置的日志记录器(log
),或者完全不生成(off
)。
打印的横幅被注册为一个单例 bean,名称为:springBootBanner
。
application.title
、application.version
和 application.formatted-version
属性仅在您使用 java -jar
或 java -cp
与 Spring Boot 启动器时可用。如果您运行的是解压后的 jar 包并使用 java -cp <classpath> <mainclass>
启动,或者将应用程序作为本机镜像运行,则这些值不会被解析。
要使用 application.
属性,请将应用程序作为打包的 jar 包使用 java -jar
启动,或作为解压后的 jar 包使用 java org.springframework.boot.loader.launch.JarLauncher
启动。这将在构建类路径并启动应用程序之前初始化 application.
横幅属性。
自定义 SpringApplication
如果 SpringApplication 的默认设置不符合你的需求,你可以创建一个本地实例并进行自定义。例如,要关闭横幅,你可以这样写:
- Java
- Kotlin
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
}
}
传递给 SpringApplication 的构造函数参数是 Spring bean 的配置源。在大多数情况下,这些是对 @Configuration 类的引用,但它们也可能是对 @Component 类的直接引用。
也可以通过使用 application.properties
文件来配置 SpringApplication。详情请参阅 外部化配置。
如需查看完整的配置选项列表,请参阅 SpringApplication API 文档。
Fluent Builder API
如果你需要构建一个 ApplicationContext 层次结构(具有父子关系的多个上下文),或者你更倾向于使用流畅的构建器 API,你可以使用 SpringApplicationBuilder。
SpringApplicationBuilder 允许你将多个方法调用链式连接在一起,并且包含 parent
和 child
方法,这些方法允许你创建一个层级结构,如下例所示:
- Java
- Kotlin
new SpringApplicationBuilder().sources(Parent.class)
.child(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
SpringApplicationBuilder()
.sources(Parent::class.java)
.child(Application::class.java)
.bannerMode(Banner.Mode.OFF)
.run(*args)
在创建 ApplicationContext 层次结构时存在一些限制。例如,Web 组件必须包含在子上下文中,并且父上下文和子上下文使用相同的 Environment。完整详细信息请参阅 SpringApplicationBuilder API 文档。
应用可用性
当部署在平台上时,应用程序可以通过使用诸如 Kubernetes 探针 等基础设施向平台提供其可用性信息。Spring Boot 为常用的“活跃度”(liveness)和“就绪度”(readiness)可用性状态提供了开箱即用的支持。如果你使用 Spring Boot 的“actuator”支持,这些状态将作为健康端点组暴露出来。
此外,你还可以通过将 ApplicationAvailability 接口注入到你自己的 Bean 中来获取应用的状态。
活跃状态
应用程序的“活跃性”(Liveness)状态表明其内部状态是否允许其正常工作,或者如果当前出现故障,是否能够自行恢复。如果“活跃性”状态被破坏,意味着应用程序处于无法恢复的状态,基础设施应重新启动该应用程序。
通常情况下,“Liveness”状态不应基于外部检查,例如健康检查。如果这样做,外部系统(如数据库、Web API、外部缓存)的故障将触发平台内的大规模重启和级联故障。
Spring Boot 应用程序的内部状态主要由 Spring ApplicationContext 表示。如果应用程序上下文成功启动,Spring Boot 会认为应用程序处于有效状态。一旦上下文被刷新,应用程序即被视为存活,请参阅 Spring Boot 应用程序生命周期及相关应用程序事件。
就绪状态
应用程序的“就绪”(Readiness)状态指示该应用程序是否准备好处理流量。如果“就绪”状态失败,平台将暂时不会将流量路由到该应用程序。这种情况通常发生在启动期间,当 CommandLineRunner 和 ApplicationRunner 组件正在处理时,或者如果应用程序决定它太忙无法处理额外的流量时,也可能在任何时候发生。
应用程序在应用程序和命令行运行器被调用后即被视为准备就绪,详情请参见 Spring Boot 应用生命周期及相关应用事件。
预期在启动期间运行的任务应该由 CommandLineRunner 和 ApplicationRunner 组件来执行,而不是使用 Spring 组件的生命周期回调,例如 @PostConstruct。
管理应用程序可用性状态
应用程序组件可以随时通过注入 ApplicationAvailability 接口并调用其方法来检索当前的可用性状态。更常见的是,应用程序会希望监听状态更新或更新应用程序的状态。
例如,我们可以将应用程序的“就绪”状态导出到一个文件中,以便 Kubernetes 的“exec 探针”可以查看该文件:
- Java
- Kotlin
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@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
}
}
}
}
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.ReadinessState
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component
@Component
class MyReadinessStateExporter {
@EventListener
fun onStateChange(event: AvailabilityChangeEvent<ReadinessState?>) {
when (event.state) {
ReadinessState.ACCEPTING_TRAFFIC -> {
// create file /tmp/healthy
}
ReadinessState.REFUSING_TRAFFIC -> {
// remove file /tmp/healthy
}
else -> {
// ...
}
}
}
}
当应用程序崩溃且无法恢复时,我们也可以更新应用程序的状态:
- Java
- Kotlin
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@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);
}
}
}
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.LivenessState
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component
@Component
class MyLocalCacheVerifier(private val eventPublisher: ApplicationEventPublisher) {
fun checkLocalCache() {
try {
// ...
} catch (ex: CacheCompletelyBrokenException) {
AvailabilityChangeEvent.publish(eventPublisher, ex, LivenessState.BROKEN)
}
}
}
应用事件与监听器
除了常见的 Spring Framework 事件,例如 ContextRefreshedEvent,SpringApplication 还会发送一些额外的应用程序事件。
有些事件实际上是在 ApplicationContext 创建之前触发的,因此你无法将这些监听器注册为 @Bean。你可以通过 SpringApplication.addListeners(…)
方法或 SpringApplicationBuilder.listeners(…)
方法来注册它们。
如果你希望无论应用程序以何种方式创建,这些监听器都能自动注册,你可以在项目中添加一个 META-INF/spring.factories
文件,并通过 ApplicationListener 键来引用你的监听器,如下例所示:
org.springframework.context.ApplicationListener=com.example.project.MyListener
应用程序事件在程序运行时会按照以下顺序发送:
-
在运行开始时,但在任何处理之前(除了注册监听器和初始化器),会发送一个 ApplicationStartingEvent。
-
当上下文中要使用的 Environment 已知但上下文尚未创建时,会发送一个 ApplicationEnvironmentPreparedEvent。
-
当 ApplicationContext 准备就绪并且已经调用了 ApplicationContextInitializers 但尚未加载任何 bean 定义时,会发送一个 ApplicationContextInitializedEvent。
-
在刷新开始之前但在 bean 定义加载之后,会发送一个 ApplicationPreparedEvent。
-
在上下文刷新之后但在调用任何应用程序和命令行运行器之前,会发送一个 ApplicationStartedEvent。
-
紧接着会发送一个带有 LivenessState.CORRECT 的 AvailabilityChangeEvent,以指示应用程序被认为是存活的。
-
在调用任何应用程序和命令行运行器之后,会发送一个 ApplicationReadyEvent。
-
紧接着会发送一个带有 ReadinessState.ACCEPTING_TRAFFIC 的 AvailabilityChangeEvent,以指示应用程序已准备好处理请求。
-
如果在启动时发生异常,会发送一个 ApplicationFailedEvent。
上述列表仅包含与 SpringApplication 相关联的 SpringApplicationEvent
。除此之外,在 ApplicationPreparedEvent 之后和 ApplicationStartedEvent 之前,还会发布以下事件:
-
在 WebServer 准备就绪后,会发送一个 WebServerInitializedEvent。ServletWebServerInitializedEvent 和 ReactiveWebServerInitializedEvent 分别是其 Servlet 和 Reactive 的变体。
-
当 ApplicationContext 刷新时,会发送一个 ContextRefreshedEvent。
通常你不需要使用应用事件,但了解它们的存在可能会很有用。在内部,Spring Boot 使用事件来处理各种任务。
事件监听器默认在同一个线程中执行,因此不应运行可能耗时的任务。考虑使用应用程序和命令行运行器代替。
应用程序事件通过使用 Spring Framework 的事件发布机制来发送。该机制的一部分确保了在子上下文中发布给监听器的事件也会发布给任何祖先上下文中的监听器。因此,如果你的应用程序使用了 SpringApplication 实例的层次结构,监听器可能会接收到多个相同类型的应用程序事件实例。
为了允许您的监听器区分其上下文的事件与后代上下文的事件,它应请求注入其应用程序上下文,然后将注入的上下文与事件的上下文进行比较。可以通过实现 ApplicationContextAware 来注入上下文,或者如果监听器是一个 bean,则可以使用 @Autowired 进行注入。
Web 环境
SpringApplication 会尝试为您创建正确类型的 ApplicationContext。用于确定 WebApplicationType 的算法如下:
-
如果存在 Spring MVC,则使用 AnnotationConfigServletWebServerApplicationContext
-
如果不存在 Spring MVC 但存在 Spring WebFlux,则使用 AnnotationConfigReactiveWebServerApplicationContext
这意味着,如果您在同一应用程序中同时使用 Spring MVC 和 Spring WebFlux 中的新 WebClient,默认情况下将使用 Spring MVC。您可以通过调用 setWebApplicationType(WebApplicationType)
轻松地覆盖此行为。
也可以通过调用 setApplicationContextFactory(…)
来完全控制所使用的 ApplicationContext 类型。
在使用 SpringApplication 进行 JUnit 测试时,通常建议调用 setWebApplicationType(WebApplicationType.NONE)
。
访问应用程序参数
如果你需要访问传递给 SpringApplication.run(…)
的应用程序参数,你可以注入一个 ApplicationArguments bean。ApplicationArguments 接口提供了对原始的 String[]
参数以及解析后的 option
和 non-option
参数的访问,如下例所示:
- Java
- Kotlin
import java.util.List;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;
@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"]
}
}
import org.springframework.boot.ApplicationArguments
import org.springframework.stereotype.Component
@Component
class MyBean(args: ApplicationArguments) {
init {
val debug = args.containsOption("debug")
val files = args.nonOptionArgs
if (debug) {
println(files)
}
// if run with "--debug logfile.txt" prints ["logfile.txt"]
}
}
Spring Boot 还会向 Spring Environment 注册一个 CommandLinePropertySource。这使得你还可以通过使用 @Value 注解来注入单个应用程序参数。
使用 ApplicationRunner 或 CommandLineRunner
如果你需要在 SpringApplication 启动后运行一些特定的代码,你可以实现 ApplicationRunner 或 CommandLineRunner 接口。这两个接口的工作方式相同,并且都提供了一个 run
方法,该方法会在 SpringApplication.run(…)
完成之前被调用。
该合约非常适合在应用程序启动后但在开始接收流量之前应运行的任务。
CommandLineRunner 接口提供了以字符串数组的形式访问应用程序参数的功能,而 ApplicationRunner 则使用了之前讨论过的 ApplicationArguments 接口。以下示例展示了一个带有 run
方法的 CommandLineRunner:
- Java
- Kotlin
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
// Do something...
}
}
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
class MyCommandLineRunner : CommandLineRunner {
override fun run(vararg args: String) {
// Do something...
}
}
如果定义了多个 CommandLineRunner 或 ApplicationRunner beans,并且它们需要按照特定顺序调用,你可以额外实现 Ordered 接口或使用 Order 注解。
应用程序退出
每个 SpringApplication 都会向 JVM 注册一个关闭钩子,以确保 ApplicationContext 在退出时能够优雅地关闭。所有标准的 Spring 生命周期回调(例如 DisposableBean 接口或 @PreDestroy 注解)都可以使用。
此外,如果希望在调用 SpringApplication.exit()
时返回特定的退出代码,Bean 可以实现 ExitCodeGenerator 接口。然后可以将此退出代码传递给 System.exit()
以将其作为状态码返回,如下例所示:
- Java
- Kotlin
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@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)));
}
}
import org.springframework.boot.ExitCodeGenerator
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import kotlin.system.exitProcess
@SpringBootApplication
class MyApplication {
@Bean
fun exitCodeGenerator() = ExitCodeGenerator { 42 }
}
fun main(args: Array<String>) {
exitProcess(SpringApplication.exit(
runApplication<MyApplication>(*args)))
}
此外,ExitCodeGenerator 接口可以由异常类实现。当遇到此类异常时,Spring Boot 会返回由实现的 getExitCode()
方法提供的退出码。
如果存在多个 ExitCodeGenerator,则使用生成的第一个非零退出码。为了控制生成器的调用顺序,可以额外实现 Ordered 接口或使用 Order 注解。
管理功能
可以通过指定 spring.application.admin.enabled
属性来启用应用程序的管理相关功能。这会在平台的 MBeanServer 上暴露 SpringApplicationAdminMXBean。你可以使用此功能远程管理你的 Spring Boot 应用程序。此功能对于任何服务包装器实现也可能非常有用。
如果你想了解应用程序运行在哪个 HTTP 端口上,可以通过键 local.server.port
获取该属性。
应用程序启动跟踪
在应用启动期间,SpringApplication 和 ApplicationContext 会执行许多与应用生命周期、Bean 生命周期甚至处理应用事件相关的任务。通过 ApplicationStartup,Spring Framework 允许你使用 StartupStep 对象来跟踪应用启动序列。这些数据可以用于性能分析,或者只是为了更好地理解应用启动过程。
你可以在设置 SpringApplication 实例时选择一个 ApplicationStartup 实现。例如,要使用 BufferingApplicationStartup,你可以这样写:
- Java
- Kotlin
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.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);
}
}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup
import org.springframework.boot.runApplication
@SpringBootApplication
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args) {
applicationStartup = BufferingApplicationStartup(2048)
}
}
第一个可用的实现,FlightRecorderApplicationStartup 由 Spring Framework 提供。它将 Spring 特定的启动事件添加到 Java Flight Recorder 会话中,用于分析应用程序并将其 Spring 上下文生命周期与 JVM 事件(如内存分配、垃圾回收、类加载等)关联起来。一旦配置完成,您可以通过启用 Flight Recorder 运行应用程序来记录数据:
$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar
Spring Boot 附带了一个 BufferingApplicationStartup 变体;这个实现旨在缓冲启动步骤并将其输出到外部指标系统中。应用程序可以在任何组件中请求类型为 BufferingApplicationStartup 的 Bean。
Spring Boot 也可以配置为暴露一个 启动端点,该端点会以 JSON 文档的形式提供这些信息。
虚拟线程
如果你在 Java 21 或更高版本上运行,可以通过将 spring.threads.virtual.enabled
属性设置为 true
来启用虚拟线程。
在为你的应用程序启用此选项之前,你应该考虑阅读官方的 Java 虚拟线程文档。在某些情况下,应用程序可能会因为“Pinned Virtual Threads”而经历吞吐量下降;该页面还解释了如何使用 JDK Flight Recorder 或 jcmd
CLI 检测此类情况。
如果启用了虚拟线程,配置线程池的属性将不再生效。这是因为虚拟线程是在 JVM 范围内的平台线程池上调度,而不是在专用的线程池上。
虚拟线程的一个副作用是它们是守护线程。如果 JVM 的所有线程都是守护线程,那么 JVM 将会退出。当你依赖 @Scheduled 等 bean 来保持应用运行时,这种行为可能会带来问题。如果你使用了虚拟线程,调度器线程就是一个虚拟线程,因此也是一个守护线程,不会保持 JVM 的运行。这不仅影响调度,其他技术也可能出现类似情况。为了在所有情况下保持 JVM 运行,建议将属性 spring.main.keep-alive
设置为 true
。这可以确保即使所有线程都是虚拟线程,JVM 也会保持运行。