使用 Picocli 的命令行模式
扩展
在配置好 Quarkus 项目后,您可以在项目根目录下运行以下命令,将 picocli
扩展添加到项目中。
quarkus extension add picocli
./mvnw quarkus:add-extension -Dextensions='picocli'
./gradlew addExtension --extensions='picocli'
这会将以下内容添加到您的构建文件中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-picocli</artifactId>
</dependency>
implementation("io.quarkus:quarkus-picocli")
构建命令行应用程序
简单应用程序
可以用如下方式创建一个只有单个 Command
的简单 Picocli 应用程序:
package com.acme.picocli;
import picocli.CommandLine;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
@CommandLine.Command (1)
public class HelloCommand implements Runnable {
@CommandLine.Option(names = {"-n", "--name"}, description = "Who will we greet?", defaultValue = "World")
String name;
private final GreetingService greetingService;
public HelloCommand(GreetingService greetingService) { (2)
this.greetingService = greetingService;
}
@Override
public void run() {
greetingService.sayHello(name);
}
}
@Dependent
class GreetingService {
void sayHello(String name) {
System.out.println("Hello " + name + "!");
}
}
1 | 如果只有一个类用 picocli.CommandLine.Command 标注,它将自动用作命令行应用程序的入口点。 |
2 | 所有用 picocli.CommandLine.Command 标注的类都将注册为 CDI bean。 |
用 @CommandLine.Command 标注的 Bean 不应使用代理作用域(例如,不要使用 @ApplicationScoped ),因为 Picocli 将无法在这些 Bean 中设置字段值。默认情况下,此 Picocli 扩展将用 @Dependent 作用域注册用 @CommandLine.Command 标注的类。如果您需要使用代理作用域,请标注 setter 而不是字段,例如: |
@CommandLine.Command
@ApplicationScoped
public class EntryCommand {
private String name;
@CommandLine.Option(names = "-n")
public void setName(String name) {
this.name = name;
}
}
具有多个 Command 的命令行应用程序
当有多个类具有 picocli.CommandLine.Command
注解时,其中一个需要同时用 io.quarkus.picocli.runtime.annotations.TopCommand
标注。这可以通过 quarkus.picocli.top-command
属性覆盖。
package com.acme.picocli;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;
@TopCommand
@CommandLine.Command(mixinStandardHelpOptions = true, subcommands = {HelloCommand.class, GoodByeCommand.class})
public class EntryCommand {
}
@CommandLine.Command(name = "hello", description = "Greet World!")
class HelloCommand implements Runnable {
@Override
public void run() {
System.out.println("Hello World!");
}
}
@CommandLine.Command(name = "goodbye", description = "Say goodbye to World!")
class GoodByeCommand implements Runnable {
@Override
public void run() {
System.out.println("Goodbye World!");
}
}
自定义 Picocli CommandLine 实例
您可以通过创建自己的 Bean 实例来定制 picocli
扩展使用的 CommandLine 类。
package com.acme.picocli;
import io.quarkus.picocli.runtime.PicocliCommandLineFactory;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
@TopCommand
@CommandLine.Command
public class EntryCommand implements Runnable {
@CommandLine.Spec
CommandLine.Model.CommandSpec spec;
@Override
public void run() {
System.out.println("My name is: " + spec.name());
}
}
@ApplicationScoped
class CustomConfiguration {
@Produces
CommandLine customCommandLine(PicocliCommandLineFactory factory) { (1)
return factory.create().setCommandName("CustomizedName");
}
}
1 | PicocliCommandLineFactory 将创建一个 CommandLine 实例,并注入 TopCommand 和 CommandLine.IFactory 。 |
为每个 Profile 设置不同的入口命令
可以使用 @IfBuildProfile
为每个 Profile 设置不同的入口命令。
@ApplicationScoped
public class Config {
@Produces
@TopCommand
@IfBuildProfile("dev")
public Object devCommand() {
return DevCommand.class; (1)
}
@Produces
@TopCommand
@IfBuildProfile("prod")
public Object prodCommand() {
return new ProdCommand("Configured by me!");
}
}
1 | 在这里您可以返回 java.lang.Class 的实例。在这种情况下,CommandLine 将尝试使用 CommandLine.IFactory 来实例化该类。 |
使用解析的参数配置 CDI Bean
您可以使用 Event<CommandLine.ParseResult>
或直接使用 CommandLine.ParseResult
来根据 Picocli 解析的参数配置 CDI Bean。此事件将在扩展创建的 QuarkusApplication
类中生成。如果您提供自己的 @QuarkusMain
,则不会触发此事件。CommandLine.ParseResult
是从默认的 CommandLine
Bean 创建的。
@CommandLine.Command
public class EntryCommand implements Runnable {
@CommandLine.Option(names = "-c", description = "JDBC connection string")
String connectionString;
@Inject
DataSource dataSource;
@Override
public void run() {
try (Connection c = dataSource.getConnection()) {
// Do something
} catch (SQLException throwables) {
// Handle error
}
}
}
@ApplicationScoped
class DatasourceConfiguration {
@Produces
@ApplicationScoped (1)
DataSource dataSource(CommandLine.ParseResult parseResult) {
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setURL(parseResult.matchedOption("c").getValue().toString());
return ds;
}
}
1 | @ApplicationScoped 用于延迟初始化 |
提供您自己的 QuarkusMain
您还可以提供自己的、用 QuarkusMain
标注的应用程序入口点(如 命令模式参考指南中所述)。
package com.acme.picocli;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
import picocli.CommandLine;
import jakarta.inject.Inject;
@QuarkusMain
@CommandLine.Command(name = "demo", mixinStandardHelpOptions = true)
public class ExampleApp implements Runnable, QuarkusApplication {
@Inject
CommandLine.IFactory factory; (1)
@Override
public void run() {
// business logic
}
@Override
public int run(String... args) throws Exception {
return new CommandLine(this, factory).execute(args);
}
}
1 | 由 picocli 扩展创建的 Quarkus 兼容 CommandLine.IFactory Bean。 |
开发模式
在开发模式下,即运行 mvn quarkus:dev
时,应用程序将在每次按下 空格键
时执行并重新启动。您还可以通过 quarkus.args
系统属性为您的命令行应用程序传递参数,例如 mvn quarkus:dev -Dquarkus.args='--help'
和 mvn quarkus:dev -Dquarkus.args='-c -w --val 1'
。对于 Gradle 项目,可以使用 --quarkus-args
传递参数。
如果您正在创建一个典型的 Quarkus 应用程序(例如,基于 HTTP 的服务)并且包含命令行功能,您需要以不同的方式处理应用程序的生命周期。在命令的 |
打包您的应用程序
Picocli 命令行应用程序可以打包成多种格式(例如,JAR、原生可执行文件),并可以发布到各种存储库(例如,Homebrew、Chocolatey、SDKMAN!)。
作为 JAR
Picocli 命令行应用程序是一个标准的 Quarkus 应用程序,因此可以以各种打包格式(例如,fast-jar、uber-jar)发布为 JAR。
在命令行应用程序的上下文中,如果您计划将 JAR 原样发布,构建 uber-jar 会更实用。
有关如何构建 uber-jar 的更多信息,请参阅我们的文档。
然后您可以使用标准的 java -jar your-application.jar
命令执行应用程序。
使用诸如 really-executable-jar-maven-plugin 等插件可以方便地简化命令行应用程序的执行。
作为原生可执行文件
您还可以构建 原生可执行文件,但请记住,原生可执行文件不是可移植的,并且您需要每个支持的平台一个二进制文件。
发布应用程序
将您的命令行应用程序发布到存储库可以大大简化其使用。根据您的需求,有各种应用程序存储库可用,例如适用于 macOS 的 SDKMAN!、Homebrew,或者适用于 Windows 的 Chocolatey。
为了发布到这些存储库,我们推荐使用 JReleaser。
JReleaser 会在您的 JAR 周围添加可执行包装器,以便轻松执行您的应用程序。
更多信息
您还可以查阅 Picocli 官方文档,以获取有关如何打包 Picocli 应用程序的一般信息。
Kubernetes 支持
一旦有了命令行应用程序,您还可以通过添加 kubernetes
扩展来生成在 Kubernetes 中安装和使用此应用程序所需的资源。要安装 kubernetes
扩展,请在项目根目录下运行以下命令。
quarkus extension add kubernetes
./mvnw quarkus:add-extension -Dextensions='kubernetes'
./gradlew addExtension --extensions='kubernetes'
这会将以下内容添加到您的 pom.xml
中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
然后,使用以下命令构建应用程序:
quarkus build
./mvnw install
./gradlew build
Kubernetes 扩展将检测到 Picocli 扩展的存在,因此将在 target/kubernetes/
目录中生成一个 Job 资源,而不是一个 Deployment 资源。
如果您不想生成 Job 资源,可以使用 quarkus.kubernetes.deployment-kind 属性指定要生成的资源。例如,如果您想生成 Deployment 资源,请使用 quarkus.kubernetes.deployment-kind=Deployment 。 |
此外,您可以通过 quarkus.kubernetes.arguments
属性提供 Kubernetes Job 将使用的参数。例如,在添加属性 quarkus.kubernetes.arguments=A,B
并构建项目后,将生成以下 Job 资源:
apiVersion: batch/v1
kind: Job
metadata:
labels:
app.kubernetes.io/name: app
app.kubernetes.io/version: 0.1-SNAPSHOT
name: app
spec:
completionMode: NonIndexed
suspend: false
template:
metadata:
labels:
app.kubernetes.io/name: app
app.kubernetes.io/version: 0.1-SNAPSHOT
spec:
containers:
- args:
- A
- B
env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: docker.io/user/app:0.1-SNAPSHOT
imagePullPolicy: Always
name: app
ports:
- containerPort: 8080
name: http
protocol: TCP
restartPolicy: OnFailure
terminationGracePeriodSeconds: 10
最后,每次在 Kubernetes 中安装时,Kubernetes Job 都将启动。您可以在此 文档中了解有关如何运行 Kubernetes Job 的更多信息。