命令行模式应用程序
本参考文档介绍如何编写运行后即退出的应用程序。
解决方案
我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 存储库: git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载一个存档。
解决方案位于 getting-started-command-mode
目录中。
创建 Maven 项目
首先,我们需要创建一个新的 Quarkus 项目,命令如下:
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数用双引号括起来,例如"-DprojectArtifactId=command-mode-quickstart"
。
建议的项目创建命令将禁用代码启动器,以避免包含 REST 服务器。同样,如果您使用 code.quarkus.io 生成项目,您需要转到 **更多选项 → 启动器代码** 并选择 **否**,以避免添加 Quarkus REST(以前称为 RESTEasy Reactive)扩展。 |
Quarkus REST 扩展仅在您请求代码启动器且未指定任何扩展时自动添加。
编写命令模式应用程序
有两种不同的方法可用于实现将退出的应用程序。
-
实现
QuarkusApplication
并让 Quarkus 自动运行此方法 -
实现
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 方法就会被调用,当它完成后,应用程序就会停止。 |
主方法
如果我们想使用 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 注解,并且名称不唯一,构建将失败。 |
命令模式生命周期
运行命令模式应用程序时的基本生命周期如下:
-
启动 Quarkus
-
运行
QuarkusApplication
主方法 -
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
开发模式
此外,对于命令模式应用程序,也支持开发模式。当您在开发模式下启动应用程序时,将执行命令模式应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
由于命令模式应用程序通常需要在命令行上传递参数,因此在开发模式下也可以这样做:
quarkus dev '--help'
./mvnw quarkus:dev -Dquarkus.args='--help'
./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。
例如,在以下测试中,启动的应用程序将接收一个模拟的单例 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 的范围应与原始 bean 一致。 |
使用此模式,您可以为任何给定的测试启用特定的替代 bean。