Application Initialization and Termination

您通常需要在应用程序启动时执行自定义操作,并在应用程序停止时清除所有内容。本指南解释了如何:

  • 编写带有 main 方法的 Quarkus 应用程序

  • 编写运行任务然后终止的命令模式应用程序

  • 在应用程序启动时收到通知

  • 通知程序停止

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 lifecycle-quickstart directory 中。

Creating the Maven project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

它生成:

  • the Maven structure

  • 针对 nativejvm 模式的 Dockerfile 示例文件

  • the application configuration file

The main method

默认情况下,Quarkus 将自动生成一个主函数,它将引导 Quarkus,然后等待启动时关闭。让我们提供我们自己的主函数:

package com.acme;

import io.quarkus.runtime.annotations.QuarkusMain;
import io.quarkus.runtime.Quarkus;

@QuarkusMain  1
public class Main {

    public static void main(String ... args) {
        System.out.println("Running main method");
        Quarkus.run(args); 2
    }
}
1 此注释告诉 Quarkus 将此作为主函数使用,除非在配置中覆盖它
2 This launches Quarkus

此主类将引导 Quarkus 并运行它,直到它停止。这与自动生成的主类没有区别,但优点是您可以直接从 IDE 启动它,而无需运行 Maven 或 Gradle 命令。

不建议在此主函数中执行任何业务逻辑,因为 Quarkus 尚未设置,并且 Quarkus 可能会在不同的 ClassLoader 中运行。如果您想在启动时执行逻辑,请使用如下所述的 io.quarkus.runtime.QuarkusApplication

如果我们想在启动时实际执行业务逻辑(或编写完成任务然后退出的应用程序),我们需要向 run 方法提供一个 io.quarkus.runtime.QuarkusApplication 类。在 Quarkus 启动之后,将调用应用程序的 run 方法。当此方法返回时,Quarkus 应用程序将退出。

如果要在启动时执行逻辑,则应该调用 Quarkus.waitForExit(),此方法将一直等到请求关闭(来自外部信号,例如在按下 Ctrl+C 时或因为某个线程调用了 Quarkus.asyncExit() 时)。

下面是一个类似示例:

package com.acme;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class Main {
    public static void main(String... args) {
        Quarkus.run(MyApp.class, args);
    }

    public static class MyApp implements QuarkusApplication {

        @Override
        public int run(String... args) throws Exception {
            System.out.println("Do startup logic here");
            Quarkus.waitForExit();
            return 0;
        }
    }
}

Quarkus.run 还提供了一个允许代码处理错误的版本。例如:

package com.acme;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class Main {
    public static void main(String... args) {
        Quarkus.run(MyApp.class,
        (exitCode, exception) -> {
            // do whatever
        },
        args);
    }

    public static class MyApp implements QuarkusApplication {

        @Override
        public int run(String... args) throws Exception {
            System.out.println("Do startup logic here");
            Quarkus.waitForExit();
            return 0;
        }
    }
}

Injecting the command line arguments

可以注入命令行中传递的参数:

@Inject
@CommandLineArguments
String[] args;

可以通过具有 quarkus.args 属性的 -D 标志将命令行参数传递给应用程序:

  • For Quarkus dev mode:include::./_includes/devtools/dev-parameters.adoc[]

  • 对于跑步者 jar: java -Dquarkus.args=<cmd-args> -jar target/quarkus-app/quarkus-run.jar

  • 对于本机可执行文件: ./target/lifecycle-quickstart-1.0-SNAPSHOT-runner -Dquarkus.args=<cmd-args>

Listening for startup and shutdown events

org.acme.lifecycle 包中创建一个名为 AppLifecycleBean 的新类(或选择另一个名称),并复制以下内容:

package org.acme.lifecycle;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import org.jboss.logging.Logger;

@ApplicationScoped
public class AppLifecycleBean {

    private static final Logger LOGGER = Logger.getLogger("ListenerBean");

    void onStart(@Observes StartupEvent ev) {               (1)
        LOGGER.info("The application is starting...");
    }

    void onStop(@Observes ShutdownEvent ev) {               (2)
        LOGGER.info("The application is stopping...");
    }

}
1 应用程序启动时调用的方法
2 应用程序终止时调用的方法

重新部署的每个事件中也会调用 dev mode 事件。

这些方法可以访问注入的 Bean。有关详细信息,请查看 AppLifecycleBean.java 类。

What is the difference from @Initialized(ApplicationScoped.class) and @Destroyed(ApplicationScoped.class)

在 JVM 模式下,除了始终通过 after @Initialized(ApplicationScoped.class) 事件触发 StartupEvent 事件,而 ShutdownEvent 事件通过 before @Destroyed(ApplicationScoped.class) 事件触发外,没有真正的区别。但是,对于一个本地可执行版本,@Initialized(ApplicationScoped.class) 事件作为 part of the native build process 事件触发,而 StartupEvent 事件在执行本地镜像时触发。有关更多详细信息,请参阅 Three Phases of Bootstrap and Quarkus Philosophy

在 CDI 应用程序中,当应用程序上下文初始化后,会触发带限定符 @Initialized(ApplicationScoped.class) 的事件。有关更多信息,请参见 the spec

Using @Startup to initialize a CDI bean at application startup

@Startup 注释表示的类、创建者方法或字段的 Bean 在应用程序启动时进行初始化:

package org.acme.lifecycle;

import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;

@Startup (1)
@ApplicationScoped
public class EagerAppBean {

   private final String name;

   EagerAppBean(NameGenerator generator) { (2)
     this.name = generator.createName();
   }
}
1 对于用 @Startup 注释的每个 Bean,都会生成一个 StartupEvent 的综合观察者。会使用默认优先级。
2 当应用程序启动时,会调用 Bean 构造函数,并将生成的上下文实例存储在应用程序上下文中。

@Dependent Bean 紧接着被销毁,以便遵循在 @Dependent Bean 中声明的观察者的行为。

如果类用 @Startup 注释,但未用作用域注释,则会自动添加 @ApplicationScoped

@Startup 注释也可以声明在一个非静态非创建者无参数方法上:

package org.acme.lifecycle;

import io.quarkus.runtime.Startup;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class EagerAppBean {

   @Startup
   void init() { 1
     doSomeCoolInit();
   }
}
1 此 Bean 在应用程序启动时创建,且 init() 方法在上下文实例上调用。

Using @Shutdown to execute a business method of a CDI bean during application shutdown

@io.quarkus.runtime.Shutdown 注释用于标记一个 CDI Bean 的业务方法,该方法应该在应用程序关闭期间执行。带注释的方法必须为非私有且非静态,且不声明参数。此行为与声明一个 ShutdownEvent 观察者类似。以下示例在功能上是等效的。

import io.quarkus.runtime.Shutdown;
import io.quarkus.runtime.ShutdownEvent;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
class Bean1 {
   void onShutdown(@Observes ShutdownEvent event) {
      // place the logic here
   }
}

@ApplicationScoped
class Bean2 {

   @Shutdown
   void shutdown() {
      // place the logic here
   }
}

Package and run the application

使用以下内容运行应用程序:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

会打印日志消息。当应用程序停止时,会打印第二个日志消息。

和往常一样,可以使用以下命令打包应用程序:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

并使用 java -jar target/quarkus-app/quarkus-run.jar 执行。

您也可以使用以下操作生成本机可执行文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

Launch Modes

Quarkus 有 3 种不同的启动模式,NORMAL(即生产)、DEVELOPMENTTEST。如果您正在运行 quarkus:dev,则模式将为 DEVELOPMENT;如果您正在运行 JUnit 测试,则模式将为 TEST;否则将为 NORMAL

您的应用程序可以通过将 io.quarkus.runtime.LaunchMode 枚举注入到 CDI Bean 中,或通过调用静态方法 io.quarkus.runtime.LaunchMode.current(),来获取启动模式。

Graceful Shutdown

Quarkus 包括对平稳关机的支持,这允许 Quarkus 等待运行请求完成,直到设定超时。默认情况下,此功能处于禁用状态,但是,您可以通过设置 quarkus.shutdown.timeout 配置属性来配置此功能。当设置此属性后,关机不会在以下时间发生:所有运行的请求都已完成或此超时已过时。

接受请求的扩展需要一个个添加对此功能的支持。现在,仅 HTTP 扩展支持此功能,因此当消息请求处于活动状态时仍可能发生关机。

Quarkus 支持延迟时间,在此延迟期间应用程序实例仍对请求做出响应,但就绪探查失败。这可以给基础设施时间来识别实例正在关闭并停止向实例路由流量。通过将编译时属性 quarkus.shutdown.delay-enabled 设置为 true,可以启用此功能。然后,可以通过设置运行时属性 quarkus.shutdown.delay 来配置延迟。默认情况下未设置此属性,因此不应用延迟。

要编写持续时间值,需使用标准格式 java.time.Duration.更多信息,请参阅 Duration#parse() javadoc. 你还可以使用简化格式,从数字开始:

  • 如果该值为数字,则表示时间(以秒计)。

  • 如果该值为数字,后跟 ms,则表示时间(以毫秒计)。

在其他情况下,简化格式会转换为 java.time.Duration 格式进行解析:

  • 如果该值为数字,后跟 h, ms, 则前缀为 PT.

  • 如果该值为数字,后跟 d ,则前缀为 P.