Command Mode Applications

Solution

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

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

解决方案位于 getting-started-command-mode directory

Creating the Maven project

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

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

建议的项目创建命令行禁用了 codestarts,以避免包含 REST 服务器。类似地,如果您使用 code.quarkus.io 生成项目,您需要转到 MORE OPTIONS → Starter Code*并选择 *No,以避免添加 Quarkus REST(以前的 RESTEasy Reactive)扩展。

只有在您要求提供 codestarts 并且您没有指定任何扩展时,Quarkus REST 扩展才会自动添加。

Writing Command Mode Applications

可以使用两种不同的方法来实现退出的应用程序。

  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`方法,并且该应用程序会在该方法结束后停止。

Contexts

Got a ContextNotActiveException?

命令模式应用程序(如 CLI)与 HTTP 服务略有不同,命令行中有一个单独的调用。因此,_request_的概念本身就不存在多个请求。因此,请求范围不会是默认值。 若要访问应用程序 Bean 和服务,请注意 @QuarkusMain`实例默认情况下是一个应用程序范围的 Bean。它有权访问单例、应用程序和依赖项作用域 bean。 如果你想与需要请求范围的 Bean 进行交互,只需在 `run()`方法上添加 `@ActivateRequestContext`注解。这使 `run()`有权访问 Panache 实体上的 `listAll()`和 `query*`方法。如果没有它,最终将在访问此类/bean 时获得 `ContextNotActiveException

Main method

如果我们希望使用 Java 主函数运行应用程序主体,它将如下所示:

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 主函数,那么将运行 Java 主函数。

建议 Java 主函数执行非常少的逻辑,并且仅启动应用程序主函数。在开发模式下,Java 主函数将在一个不同于主应用程序的 ClassLoader 中运行,因此可能无法按预期方式运行。

Multiple Main Methods

可以在应用程序中具有多个主方法,并在构建时在它们之间进行选择。`@QuarkusMain`注解带有一个可选的“名称”参数,它可用于选择主函数以使用 `quarkus.package.main-class`构建时间配置选项运行。如果你不想使用注解也能使用它来指定主类的完全限定名。

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

`@QuarkusMain`的 `name`必须唯一(包括空字符串的默认值)。如果应用程序中有多个 `@QuarkusMain`注解,在名称不唯一的情况下构建将失败。

The command mode lifecycle

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

  1. Start Quarkus

  2. 运行 `QuarkusApplication`主方法

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

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

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

Running the application

要在 JVM 上运行命令模式应用程序,首先使用 `mvnw package`或同等命令构建它。

然后启动它:

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

你也可以使用 `mvnw package -Dnative`构建一个本机应用程序,并使用类似如下命令启动它:

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

Development Mode

另外,对于命令模式应用程序,支持开发模式。在你以开发模式启动应用程序时,命令模式应用程序将被执行:

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

由于命令模式应用程序通常需要在命令行上传递参数,因此在 dev 模式下也是如此:

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>

您可以按 Space bar 键,应用程序将再次启动。您还可以使用 e 热键编辑命令行参数并重新启动您的应用程序。

Testing Command Mode Applications

可以使用 @QuarkusMainTest@QuarkusMainIntegrationTest 注释测试命令模式应用程序。这些注释的工作方式类似于 @QuarkusTest@QuarkusIntegrationTest,其中 @QuarkusMainTest 将在当前 JVM 内运行 CLI 测试,而 QuarkusIntegrationTest 用于运行生成的执行文件(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 {
}

Mocking

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

不过,可以利用 test profiles 模拟 CDI Bean。

例如,在以下测试中,单例 CdiBean1 将被 MockedCdiBean1 模拟:

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 的范围应与其原始范围一致。

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