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之外的日志级别,您可以如日志级别中所述设置它。应用程序版本是使用主应用程序类包中的实现版本确定的。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实现,您可以添加自己的.

如果故障分析器没有能够处理异常,您仍然可以显示完整的情况报告,以更好地了解出了什么问题。为此,您需要启用debug属性或对org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener启用DEBUG日志记录.

例如,如果您使用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中可用的任何键以及以下任何占位符:

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

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

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

仅当您将java -jarjava -cp 与 Spring Boot 启动器一起使用时,application.titleapplication.versionapplication.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配置。有关详细信息,请参阅外部化配置

有关配置选项的完整列表,请参阅SpringApplicationJavadoc

7.1.5. Fluent 构建器 API

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

SpringApplicationBuilder允许您将多个方法调用链接在一起,并包含可让您创建层次结构的parentchild方法,如以下示例所示:

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

创建层次结构ApplicationContext时存在一些限制。例如,Web 组件必须包含在子上下文中,并且Environment同样适用于父上下文和子上下文。有关完整详细信息, 请参阅SpringApplicationBuilderJavadoc 。

7.1.6. 应用程序可用性

当部署在平台上时,应用程序可以使用Kubernetes Probes等基础设施向平台提供有关其可用性的信息。Spring Boot 包括对常用的“活跃”和“就绪”可用性状态的开箱即用支持。如果您使用 Spring Boot 的“执行器”支持,那么这些状态将作为运行状况端点组公开。

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

活性状态

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

一般来说,“Liveness”状态不应该基于外部检查,例如Health 检查。如果确实如此,发生故障的外部系统(数据库、Web API、外部缓存)将触发整个平台的大规模重启和级联故障。

Spring Boot 应用程序的内部状态主要由 Spring ApplicationContext表示。如果应用程序上下文已成功启动,Spring Boot 会假定应用程序处于有效状态。一旦上下文刷新,应用程序就被视为处于活动状态,请参阅Spring Boot 应用程序生命周期和相关应用程序事件

准备状态

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

一旦调用应用程序和命令行运行程序,应用程序就被视为准备就绪,请参阅Spring Boot 应用程序生命周期和相关应用程序事件

预计在启动期间运行的任务应该由CommandLineRunnerApplicationRunner组件执行,而不是使用 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”您可以在专门部分中获得有关在 Kubernetes 上部署 Spring Boot 应用程序的更多指导。

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

除了常见的 Spring 框架事件(例如 )之外,例如ContextRefreshedEventSpringApplication还会发送一些其他应用程序事件。

有些事件实际上是在创建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. 在调用任何应用程序和命令行运行程序后发送ApplicationReadyEvent

  8. 紧随 ReadinessState.ACCEPTING_TRAFFIC 之后发送AvailabilityChangeEvent,表示应用程序已准备好处理请求。

  9. 如果启动时出现异常,则会发送一个ApplicationFailedEvent

上面的列表仅包含与SpringApplication 绑定的 SpringApplicationEvents 。除此之外,以下事件也在ApplicationStartedEvent 之前和ApplicationPreparedEvent之后发布:

  • WebServer就绪之后后发送WebServerInitializedEventServletWebServerInitializedEventReactiveWebServerInitializedEvent分别对应 servlet 和响应式变体的发送事件。

  • ApplicationContext刷新时发送ContextRefreshedEvent

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

事件侦听器不应运行可能很长的任务,因为默认情况下它们在同一线程中执行。考虑使用应用程序和命令行运行程序

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

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

7.1.8. 网络环境

SpringApplication代表您尝试创建正确的类型。用于确定 ApplicationContextWebApplicationType算法如下:

  • 如果存在 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.ApplicationArgumentsbean。ApplicationArguments接口提供对原始String[]参数以及解析后的optionnon-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启动后运行某些特定代码,您可以实现ApplicationRunnerCommandLineRunner接口。两个接口以相同的方式工作,并提供一个run方法,该方法在SpringApplication.run(…)完成之前调用。

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

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

爪哇

科特林

@Component
public class MyCommandLineRunner implements CommandLineRunner {

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

}

如果定义了多个CommandLineRunnerApplicationRunner必须按特定顺序调用的 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平台上的情况MBeanServer。您可以使用此功能远程管理您的 Spring Boot 应用程序。此功能对于任何服务包装器实现也很有用。

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

7.1.13. 应用程序启动跟踪

在应用程序启动期间,SpringApplicationApplicationContext将执行许多与应用程序生命周期、bean 生命周期甚至处理应用程序事件相关的任务。通过ApplicationStartup, Spring Framework允许您可以使用StartupStep对象跟踪应用程序启动顺序。收集这些数据可以用于分析目的,或者只是为了更好地了解应用程序启动过程。

可以在设置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端点,该端点以 JSON 文档的形式提供此信息。

7.1.14. 虚拟线程

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

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

最后更新于