编辑此页面

命令行模式应用程序

本参考文档介绍如何编写运行后即退出的应用程序。

解决方案

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

克隆 Git 存储库: git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载一个存档

解决方案位于 getting-started-command-mode 目录中。

创建 Maven 项目

首先,我们需要创建一个新的 Quarkus 项目,命令如下:

CLI
quarkus create app org.acme:command-mode-quickstart \
    --no-code
cd command-mode-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=command-mode-quickstart \
    -DnoCode
cd command-mode-quickstart

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

对于 Windows 用户

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

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

建议的项目创建命令将禁用代码启动器,以避免包含 REST 服务器。同样,如果您使用 code.quarkus.io 生成项目,您需要转到 **更多选项 → 启动器代码** 并选择 **否**,以避免添加 Quarkus REST(以前称为 RESTEasy Reactive)扩展。

Quarkus REST 扩展仅在您请求代码启动器且未指定任何扩展时自动添加。

编写命令模式应用程序

有两种不同的方法可用于实现将退出的应用程序。

  1. 实现 QuarkusApplication 并让 Quarkus 自动运行此方法

  2. 实现 QuarkusApplication 和 Java main 方法,并使用 Java main 方法启动 Quarkus

在本文档中,QuarkusApplication 实例被称为应用程序主程序,而具有 Java main 方法的类是 Java 主程序。

最简单的命令模式应用程序,可以访问 Quarkus API,可能如下所示:

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

@QuarkusMain    (1)
public class HelloWorldMain implements QuarkusApplication {
  @Override
  public int run(String... args) throws Exception {   (2)
    System.out.println("Hello " + args[0]);
    return 0;
 }
}
1 @QuarkusMain 注解告诉 Quarkus 这是主入口点。
2 一旦 Quarkus 启动,run 方法就会被调用,当它完成后,应用程序就会停止。

上下文

遇到 ContextNotActiveException

命令模式应用程序(如 CLI)与 HTTP 服务等有所不同,命令行只有一个调用。因此,“请求”的概念,更不用说多个请求,本身就不存在。因此,请求范围不是默认设置。

要访问您的应用程序 bean 和服务,请注意 @QuarkusMain 实例默认是应用程序范围的 bean。它可以访问单例、应用程序和依赖项范围的 bean。

如果您想与需要请求范围的 bean 进行交互,只需在您的 run() 方法上添加 @ActivateRequestContext 注解。这使 run() 可以访问 Panache 实体上的 listAll()query* 等方法。否则,在访问这些类/ bean 时,您最终会收到 ContextNotActiveException

主方法

如果我们想使用 Java main 来运行应用程序主程序,它看起来像这样:

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

@QuarkusMain
public class JavaMain {

    public static void main(String... args) {
        Quarkus.run(HelloWorldMain.class, args);
    }
}

这实际上与直接运行 HelloWorldMain 应用程序主程序相同,但优点是可以从 IDE 运行。

如果一个类实现了 QuarkusApplication 并具有 Java main,那么 Java main 将被运行。
建议 Java main 执行很少的逻辑,只需启动应用程序主程序。在开发模式下,Java main 将在与主应用程序不同的 ClassLoader 中运行,因此行为可能与您预期不同。

多个主方法

应用程序可以包含多个主方法,并在构建时进行选择。@QuarkusMain 注解接受一个可选的 'name' 参数,可以使用 quarkus.package.main-class 构建时配置选项来选择运行哪个主方法。如果您不想使用注解,也可以使用它来指定主类的完全限定名。

默认情况下,将使用没有名称(即空字符串)的 @QuarkusMain,如果它不存在且未指定 quarkus.package.main-class,则 Quarkus 将自动生成一个仅运行应用程序的主类。

@QuarkusMain 的 'name' 必须是唯一的(包括默认的空字符串)。如果您的应用程序中有多个 @QuarkusMain 注解,并且名称不唯一,构建将失败。

命令模式生命周期

运行命令模式应用程序时的基本生命周期如下:

  1. 启动 Quarkus

  2. 运行 QuarkusApplication 主方法

  3. Quarkus 关闭并退出 JVM(在主方法返回后)

关闭总是由应用程序主线程返回触发。如果您想在启动时运行一些逻辑,然后像普通应用程序一样运行(即不退出),那么您应该从主线程调用 Quarkus.waitForExit(非命令模式应用程序本质上只是运行一个调用 waitForExit 的应用程序)。

如果您想关闭正在运行的应用程序并且您不在主线程中,那么您应该调用 Quarkus.asyncExit 以便解除主线程阻塞并启动关闭过程。

运行应用程序

要在 JVM 上运行命令模式应用程序,请先使用 mvnw package 或等效命令进行构建。

然后启动它:

java -jar target/quarkus-app/quarkus-run.jar

您也可以使用 mvnw package -Dnative 构建一个原生应用程序,并使用类似以下命令启动它:

./target/getting-started-command-mode-1.0-SNAPSHOT-runner

开发模式

此外,对于命令模式应用程序,也支持开发模式。当您在开发模式下启动应用程序时,将执行命令模式应用程序:

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

由于命令模式应用程序通常需要在命令行上传递参数,因此在开发模式下也可以这样做:

CLI
quarkus dev '--help'
Maven
./mvnw quarkus:dev -Dquarkus.args='--help'
Gradle
./gradlew quarkusDev --quarkus-args='--help'

在应用程序停止后,您应该在屏幕底部看到以下内容:

--
Press [space] to restart, [e] to edit command line args (currently '-w --tags 1.0.1.Final'), [r] to resume testing, [o] Toggle test output, [h] for more options>

您可以按下 空格键 再次启动应用程序。您也可以使用 e 热键编辑命令行参数并重新启动应用程序。

测试命令模式应用程序

可以使用 @QuarkusMainTest@QuarkusMainIntegrationTest 注解来测试命令模式应用程序。这些注解的工作方式与 @QuarkusTest@QuarkusIntegrationTest 类似,其中 @QuarkusMainTest 将在当前 JVM 内运行 CLI 测试,而 QuarkusMainIntegrationTest 用于运行生成的可执行文件(JAR 包和原生可执行文件)。

我们可以为上述 CLI 应用程序编写一个简单的测试,如下所示:

import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import io.quarkus.test.junit.main.QuarkusMainLauncher;
import io.quarkus.test.junit.main.QuarkusMainTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

@QuarkusMainTest
public class HelloTest {

    @Test
    @Launch("World")
    public void testLaunchCommand(LaunchResult result) {
        Assertions.assertTrue(result.getOutput().contains("Hello World"));
    }

    @Test
    @Launch(value = {}, exitCode = 1)
    public void testLaunchCommandFailed() {
    }

    @Test
    public void testManualLaunch(QuarkusMainLauncher launcher) {
        LaunchResult result = launcher.launch("Everyone");
        Assertions.assertEquals(0, result.exitCode());
        Assertions.assertTrue(result.getOutput().contains("Hello Everyone"));
    }
}

每个测试方法都必须用 @Launch 注解来自动启动应用程序,或者有一个 QuarkusMainLauncher 参数来手动启动应用程序。

然后,我们可以通过一个集成测试来扩展这一点,该集成测试可用于测试原生可执行文件或可运行的 JAR 包:

import io.quarkus.test.junit.main.QuarkusMainIntegrationTest;

@QuarkusMainIntegrationTest
public class HelloIT extends HelloTest {
}

模拟

CDI 注入不支持在 @QuarkusMainTest 测试中进行。因此,也不支持使用 QuarkusMock@InjectMock 模拟 CDI bean。

尽管如此,通过利用 测试配置文件,可以模拟 CDI bean。

例如,在以下测试中,启动的应用程序将接收一个模拟的单例 CdiBean1MockedCdiBean1 的实现由测试提供:

package org.acme.commandmode.test;

import java.util.Set;

import jakarta.enterprise.inject.Alternative;
import jakarta.inject.Singleton;

import org.junit.jupiter.api.Test;
import org.acme.commandmode.test.MyCommandModeTest.MyTestProfile;

import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.TestProfile;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import io.quarkus.test.junit.main.QuarkusMainTest;

@QuarkusMainTest
@TestProfile(MyTestProfile.class)
public class MyCommandModeTest {

    @Test
    @Launch(value = {})
    public void testLaunchCommand(LaunchResult result) {
        // ... assertions ...
    }

    public static class MyTestProfile implements QuarkusTestProfile {

        @Override
        public Set<Class<?>> getEnabledAlternatives() {
            return Set.of(MockedCdiBean1.class); (1)
        }
    }

    @Alternative (2)
    @Singleton (3)
    public static class MockedCdiBean1 implements CdiBean1 {

        @Override
        public String myMethod() {
            return "mocked value";
        }
    }
}
1 列出您希望为其启用替代模拟 bean 的所有 CDI bean。
2 使用不带 @Priority@Alternative。确保您不使用 @Mock
3 模拟 bean 的范围应与原始 bean 一致。

使用此模式,您可以为任何给定的测试启用特定的替代 bean。

相关内容