编辑此页面

应用程序初始化和终止

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

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

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

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

  • 在应用程序停止时收到通知

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

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

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

解决方案

我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 压缩包

解决方案位于 lifecycle-quickstart 目录中。

创建 Maven 项目

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

CLI
quarkus create app org.acme:lifecycle-quickstart \
    --no-code
cd lifecycle-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=lifecycle-quickstart \
    -DnoCode
cd lifecycle-quickstart

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

对于 Windows 用户

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

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=lifecycle-quickstart"

它生成

  • Maven 结构

  • 用于 nativejvm 模式的示例 Dockerfile 文件

  • 应用程序配置文件

main 方法

默认情况下,Quarkus 会自动生成一个 main 方法,该方法将引导 Quarkus,然后等待启动关闭。 让我们提供我们自己的 main 方法

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 将其用作 main 方法,除非在配置中被覆盖
2 这将启动 Quarkus

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

不建议在此 main 方法中执行任何业务逻辑,因为 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;
        }
    }
}

注入命令行参数

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

@Inject
@CommandLineArguments
String[] args;

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

  • 对于 Quarkus 开发模式

    CLI
    quarkus dev -Dquarkus.args=cmd-args
    Maven
    ./mvnw quarkus:dev -Dquarkus.args=cmd-args
    Gradle
    ./gradlew --console=plain quarkusDev -Dquarkus.args=cmd-args
  • 对于 runner jar:java -Dquarkus.args=<cmd-args> -jar target/quarkus-app/quarkus-run.jar

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

监听启动和关闭事件

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 应用程序终止时调用的方法
这些事件也在每次重新部署之间的开发模式中调用。
这些方法可以访问注入的 bean。 请查看 AppLifecycleBean.java 类以获取详细信息。

@Initialized(ApplicationScoped.class)@Destroyed(ApplicationScoped.class) 有什么区别

在 JVM 模式下,没有真正的区别,只是 StartupEvent 始终在 @Initialized(ApplicationScoped.class) 之后触发,ShutdownEvent@Destroyed(ApplicationScoped.class) 之前触发。 但是,对于 native 可执行文件构建,@Initialized(ApplicationScoped.class) 作为 native 构建过程的一部分触发,而 StartupEvent 在执行 native 镜像时触发。 有关更多详细信息,请参见 引导和 Quarkus 哲学三个阶段

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

使用 @Startup 在应用程序启动时初始化 CDI bean

@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() 方法。

使用 @Shutdown 在应用程序关闭期间执行 CDI bean 的业务方法

@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
   }
}

打包并运行应用程序

使用以下命令运行应用程序

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 执行。

您还可以使用以下命令生成 native 可执行文件

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

启动模式

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

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

优雅关闭

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 格式以进行解析

  • 如果该值是一个数字后跟 hms,则在其前面加上 PT

  • 如果该值是一个数字后跟 d,则在其前面加上 P

相关内容