Command Mode with Picocli

Picocli是一种用于创建丰富的命令行应用程序的开源工具。 Quarkus 提供对使用 Picocli 的支持。本指南包含 `picocli`扩展用法的示例。

如果你不熟悉 Quarkus 命令模式,请考虑首先阅读Command Mode reference guide

Extension

配置好 Quarkus 项目后,可以通过在项目基础目录中运行以下命令将 `picocli`扩展添加到项目中。

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

这会将以下内容添加到构建文件中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-picocli</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-picocli")

Simple command line application

仅一个 `Command`的简单 PicocliApplication 可按如下方式创建:

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`注解的类,则将其用作 Picocli 命令行的入口点。
2 用 `picocli.CommandLine.Command`注解的所有类都将注册为 CDI bean。

具有 @CommandLine.Command`的 bean 不应使用代理范围(例如不要使用 `@ApplicationScope),因为 Picocli 无法在此类 bean 中设置字段值。此扩展将使用 `@CommandLine.Command`范围将类与 `@Depended`注解一起注册。如果你需要使用代理范围,则注解 setter 而不是字段,例如:

@CommandLine.Command
@ApplicationScoped
public class EntryCommand {
    private String name;

    @CommandLine.Option(names = "-n")
    public void setName(String name) {
        this.name = name;
    }

}

Command line application with multiple Commands

当多个类存在 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!");
    }
}

Customizing Picocli CommandLine instance

您可以通过生成自己的 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 会使用注入的 TopCommandCommandLine.IFactory 创建一个 CommandLine 实例。

Different entry command for each profile

可以使用 @IfBuildProfile 为每个配置文件创建不同的入口命令:

@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 实例化此类。

Configure CDI Beans with parsed arguments

可以使用 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 用于延迟初始化

Providing own QuarkusMain

您还可以提供自己的应用程序入口点,带有 QuarkusMain 注释(如 Command Mode reference guide 所述)。

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 CommandLine.IFactory bean 与 Quarkus 兼容,由 picocli 扩展创建。

Native mode support

此扩展使用 Quarkus 标准构建步骤机制来支持 GraalVM Native 映像。在特殊情况下,未来 picocli 版本中的不兼容更改会导致任何问题时,可以使用以下配置作为临时解决办法,以回退到来自 picocli 项目的注释处理器:

<dependency>
  <groupId>info.picocli</groupId>
  <artifactId>picocli-codegen</artifactId>
</dependency>

对于 Gradle,您需要在 build.gradle 文件的 dependencies 部分中添加以下内容:

annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
annotationProcessor 'info.picocli:picocli-codegen'

Development Mode

在开发模式下,即运行 mvn quarkus:dev 时,每次按下 Space bar 键时都会执行应用程序并重新启动。您还可以通过 quarkus.args 系统属性将参数传递给您的命令行应用程序,例如 mvn quarkus:dev -Dquarkus.args='--help'mvn quarkus:dev -Dquarkus.args='-c -w --val 1'

Kubernetes support

拥有命令行应用程序后,您还可以通过添加 kubernetes 扩展来生成在 Kubernetes 中安装和使用此应用程序所需的资源。要在您的项目基本目录下安装 kubernetes 扩展,请运行以下命令。

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

这会将以下内容添加到您的 pom.xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-kubernetes</artifactId>
</dependency>

然后,使用以下命令构建应用程序:

CLI
quarkus build
Maven
./mvnw install
Gradle
./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 中时启动。更多关于如何在 document 中运行 Kubernetes 任务的信息,请点击此处。

Configuration Reference

Unresolved include directive in modules/ROOT/pages/picocli.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-picocli.adoc[]