Command Mode Applications

本参考指南涵盖了如何编写运行然后退出的应用程序。

This reference covers how to write applications that run and then exit.

Solution

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

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

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

The solution is located in the getting-started-command-mode directory.

Creating the Maven project

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

First, we need to create a new Quarkus project with the following command:

Unresolved directive in command-mode-reference.adoc - include::{includes}/devtools/create-app.adoc[]

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

The suggested project creation command lines disable the codestarts to avoid including a REST server. Similarly, if you use code.quarkus.io to generate a project, you need to go to MORE OPTIONS → Starter Code and select No to avoid adding the Quarkus REST (formerly RESTEasy Reactive) extension.

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

The Quarkus REST extension is added automatically only if you ask for codestarts and you didn’t specify any extensions.

Writing Command Mode Applications

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

There are two different approaches that can be used to implement applications that exit.

  1. Implement QuarkusApplication and have Quarkus run this method automatically

  2. Implement QuarkusApplication and a Java main method, and use the Java main method to launch Quarkus

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

In this document the QuarkusApplication instance is referred to as the application main, and a class with a Java main method is the Java main.

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

The simplest possible command mode application with access to Quarkus APIs might appear as follows:

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 The @QuarkusMain annotation tells Quarkus that this is the main entry point.
2 The run method is invoked once Quarkus starts, and the application stops when it finishes.

Contexts

Got a ContextNotActiveException?

命令模式应用程序(如 CLI)与 HTTP 服务略有不同,命令行中有一个单独的调用。因此,_request_的概念本身就不存在多个请求。因此,请求范围不会是默认值。

A command mode application (like a CLI) is a bit different from say an HTTP service, there is a single call from the command line. So the notion of request let alone multiple requests does not exist per se. Therefore, request scope is not the default.

若要访问应用程序 Bean 和服务,请注意 `@QuarkusMain`实例默认情况下是一个应用程序范围的 Bean。它有权访问单例、应用程序和依赖项作用域 bean。

To get access to your application beans and services, be aware that a @QuarkusMain instance is an application scoped bean by default. It has access to singletons, application and dependent scoped beans.

如果你想与需要请求范围的 Bean 进行交互,只需在 run()`方法上添加 `@ActivateRequestContext`注解。这使 `run()`有权访问 Panache 实体上的 `listAll()`和 `query*`方法。如果没有它,最终将在访问此类/bean 时获得 `ContextNotActiveException

If you want to interact with beans that requires a request scope, simply add the @ActivateRequestContext annotation on your run() method. This let run() have access to methods like listAll() and query* methods on a Panache Entity. Without it, you will eventually get a ContextNotActiveException when accessing such classes/beans.

Main method

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

If we want to use a Java main to run the application main it would look like:

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 中运行。

This is effectively the same as running the HelloWorldMain application main directly, but has the advantage it can be run from the IDE.

如果某个类实现了 `QuarkusApplication`并且有 Java 主函数,那么将运行 Java 主函数。

If a class that implements QuarkusApplication and has a Java main then the Java main will be run.

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

It is recommended that a Java main perform very little logic, and just launch the application main. In development mode the Java main will run in a different ClassLoader to the main application, so may not behave as you would expect.

Multiple Main Methods

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

It is possible to have multiple main methods in an application, and select between them at build time. The @QuarkusMain annotation takes an optional 'name' parameter, and this can be used to select the main to run using the quarkus.package.main-class build time configuration option. If you don’t want to use annotations this can also be used to specify the fully qualified name of a main class.

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

By default, the @QuarkusMain with no name (i.e. the empty string) will be used, and if it is not present and quarkus.package.main-class is not specified then Quarkus will automatically generate a main class that just runs the application.

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

The name of @QuarkusMain must be unique (including the default of the empty string). If you have multiple @QuarkusMain annotations in your application the build will fail if the names are not unique.

The command mode lifecycle

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

When running a command mode application the basic lifecycle is as follows:

  1. Start Quarkus

  2. Run the QuarkusApplication main method

  3. Shut down Quarkus and exit the JVM after the main method returns

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

Shutdown is always initiated by the application main thread returning. If you want to run some logic on startup, and then run like a normal application (i.e. not exit) then you should call Quarkus.waitForExit from the main thread (A non-command mode application is essentially just running an application that just calls waitForExit).

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

If you want to shut down a running application and you are not in the main thread, then you should call Quarkus.asyncExit in order to unblock the main thread and initiate the shutdown process.

Running the application

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

To run the command mode application on the JVM, first build it using mvnw package or equivalent.

然后启动它:

Then launch it:

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

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

You can also build a native application with mvnw package -Dnative, and launch it with something like:

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

Development Mode

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

Also, for command mode applications, the dev mode is supported. When you start your application in dev mode, the command mode application is executed:

Unresolved directive in command-mode-reference.adoc - include::{includes}/devtools/dev.adoc[]

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

As command mode applications will often require arguments to be passed on the command line, this is also possible in dev mode:

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

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

You should see the following down the bottom of the screen after the application is stopped:

--
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 热键编辑命令行参数并重新启动您的应用程序。

You can press the Space bar key and the application will be started again. You can also use the e hotkey to edit the command line arguments and restart your application.

Testing Command Mode Applications

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

Command Mode applications can be tested using the @QuarkusMainTest and @QuarkusMainIntegrationTest annotations. These work in a similar way to @QuarkusTest and @QuarkusIntegrationTest where @QuarkusMainTest will run the CLI tests within the current JVM, while QuarkusIntegrationTest is used to run the generated executable (both jars and native).

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

We can write a simple test for our CLI application above as follows:

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 参数以手动启动应用程序。

Each test method must be annotated with @Launch to automatically start the application or have a QuarkusMainLauncher parameter to manually launch the application.

然后,我们可以将此功能扩展到集成测试,该测试可用于测试原生执行文件或可运行的 jar:

We can then extend this with an integration test that can be used to test the native executable or runnable jar:

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

@QuarkusMainIntegrationTest
public class HelloIT extends HelloTest {
}

Mocking

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

CDI injection is not supported in the @QuarkusMainTest tests. Consequently, mocking CDI beans with QuarkusMock or @InjectMock is not supported either.

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

It is possible to mock CDI beans by leveraging test profiles though.

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

For instance, in the following test, the singleton CdiBean1 will be mocked by 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 List all the CDI beans for which you want to enable an alternative mocked bean.
2 Use @Alternative without a @Priority. Make sure you don’t use @Mock.
3 The scope of the mocked bean should be consistent with the original one.

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

Using this pattern, you can enable specific alternatives for any given test.