1. 序章

このチュートリアルでは、 picocliライブラリにアプローチします。これにより、Javaでコマンドラインプログラムを簡単に作成できます。

まず、HelloWorldコマンドを作成することから始めます。 次に、 git コマンドを部分的に再現して、ライブラリの主要な機能について詳しく説明します。

2. Hello World Command

簡単なことから始めましょう:Hello Worldコマンド!

まず最初に、依存関係をpicocliプロジェクトに追加する必要があります。

<dependency>
    <groupId>info.picocli</groupId>
    <artifactId>picocli</artifactId>
    <version>3.9.6</version>
</dependency>

ご覧のとおり、ライブラリの 3.9.6 バージョンを使用しますが、 4.0.0 バージョンは現在作成中です(現在アルファテストで利用可能です)。

依存関係が設定されたので、HelloWorldコマンドを作成しましょう。 そのために、ライブラリの@Commandアノテーションを使用します

@Command(
  name = "hello",
  description = "Says hello"
)
public class HelloWorldCommand {
}

ご覧のとおり、アノテーションはパラメーターを取ることができます。 ここではそのうちの2つだけを使用しています。 それらの目的は、自動ヘルプメッセージの現在のコマンドとテキストに関する情報を提供することです。

現時点では、このコマンドでできることはあまりありません。 何かを実行するには、 main メソッドを追加して便利なCommandLine.run(Runnable、String [])メソッドを呼び出す必要があります。 これには2つのパラメーターが必要です。コマンドのインスタンスであるため、 Runnable インターフェースを実装する必要があります。また、コマンド引数(オプション、パラメーター、およびサブコマンド)を表すString配列です。

public class HelloWorldCommand implements Runnable {
    public static void main(String[] args) {
        CommandLine.run(new HelloWorldCommand(), args);
    }

    @Override
    public void run() {
        System.out.println("Hello World!");
    }
}

ここで、 main メソッドを実行すると、コンソールが「HelloWorld!」を出力することがわかります。

jar にパッケージ化すると、javaコマンドを使用してHelloWorldコマンドを実行できます。

java -cp "pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand

当然のことながら、「HelloWorld!」文字列もコンソールに出力されます。

3. 具体的なユースケース

基本を理解したので、picocliライブラリについて詳しく説明します。 そのために、人気のあるコマンドgitを部分的に再現します。

もちろん、目的は git コマンドの動作を実装することではなく、 git コマンドの可能性を再現することです。つまり、どのサブコマンドが存在し、どのオプションが固有のサブコマンドで使用できるかを再現します。

まず、Hello Worldコマンドの場合と同様に、GitCommandクラスを作成する必要があります。

@Command
public class GitCommand implements Runnable {
    public static void main(String[] args) {
        CommandLine.run(new GitCommand(), args);
    }

    @Override
    public void run() {
        System.out.println("The popular git command");
    }
}

4. サブコマンドの追加

git コマンドは、多くのサブコマンド add、commit、remoteなどを提供します。 ここでは、addcommitに焦点を当てます。

したがって、ここでの目標は、これら2つのサブコマンドをメインコマンドに宣言することです。  Picocli は、これを実現する3つの方法を提供します。

4.1. クラスでの@Commandアノテーションの使用

@Commandアノテーションは、サブコマンドパラメーターを介してサブコマンドを登録する可能性を提供します。

@Command(
  subcommands = {
      GitAddCommand.class,
      GitCommitCommand.class
  }
)

この例では、GitAddCommandGitCommitCommandの2つの新しいクラスを追加します。 どちらも@Commandで注釈が付けられ、Runnableを実装します。 名前を付けることが重要です。名前は、実行するサブコマンドを認識するためにpicocliによって使用されるためです。

@Command(
  name = "add"
)
public class GitAddCommand implements Runnable {
    @Override
    public void run() {
        System.out.println("Adding some files to the staging area");
    }
}

 

@Command(
  name = "commit"
)
public class GitCommitCommand implements Runnable {
    @Override
    public void run() {
        System.out.println("Committing files in the staging area, how wonderful?");
    }
}

したがって、 add を引数としてメインコマンドを実行すると、コンソールは「ステージング領域にいくつかのファイルを追加しています」を出力します。

4.2. メソッドでの@Commandアノテーションの使用

サブコマンドを宣言する別の方法は、GitCommandクラスでそれらのコマンドを表す@Command注釈付きメソッドを作成することです。

@Command(name = "add")
public void addCommand() {
    System.out.println("Adding some files to the staging area");
}

@Command(name = "commit")
public void commitCommand() {
    System.out.println("Committing files in the staging area, how wonderful?");
}

そうすれば、ビジネスロジックをメソッドに直接実装でき、それを処理するための個別のクラスを作成する必要はありません。

4.3. プログラムによるサブコマンドの追加

最後に、picocliを使用すると、サブコマンドをプログラムで登録できます。コマンドをラップする CommandLine オブジェクトを作成し、それにサブコマンドを追加する必要があるため、これは少し注意が必要です。

CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.addSubcommand("add", new GitAddCommand());
commandLine.addSubcommand("commit", new GitCommitCommand());

その後もコマンドを実行する必要がありますが、 CommandLine.run()メソッドを使用できなくなります。 ここで、新しく作成したC ommandLineオブジェクトでparseWithHandler()メソッドを呼び出す必要があります。

commandLine.parseWithHandler(new RunLast(), args);

RunLast クラスの使用に注意する必要があります。このクラスは、picocliに最も具体的なサブコマンドを実行するように指示します。 picocli によって提供される他の2つのコマンドハンドラーがあります:RunFirstRunAll。 前者は最上位のコマンドを実行し、後者はすべてのコマンドを実行します。

便利なメソッドCommandLine.run()を使用する場合、デフォルトでRunLastハンドラーが使用されます。

5. @Optionアノテーションを使用したオプションの管理

5.1. 引数のないオプション

コマンドにいくつかのオプションを追加する方法を見てみましょう。 実際、 add コマンドに、変更されたすべてのファイルを追加する必要があることを伝えたいと思います。 これを実現するために、@Optionアノテーションでアノテーションが付けられたフィールドをGitAddCommandクラスに追加します。

@Option(names = {"-A", "--all"})
private boolean allFiles;

@Override
public void run() {
    if (allFiles) {
        System.out.println("Adding all files to the staging area");
    } else {
        System.out.println("Adding some files to the staging area");
    }
}

ご覧のとおり、アノテーションは names パラメーターを取り、オプションのさまざまな名前を指定します。 したがって、 -Aまたは–allのいずれかを指定してadd コマンドを呼び出すと、allFilesフィールドがtrueに設定されます。 。 したがって、オプションを指定してコマンドを実行すると、コンソールに「すべてのファイルをステージング領域に追加しています」と表示されます。

5.2. 引数付きのオプション

先ほど見たように、引数のないオプションの場合、それらの存在または不在は常にboolean値に評価されます。

ただし、引数を取るオプションを登録することは可能です。 これは、フィールドを別のタイプとして宣言するだけで実行できます。 commitコマンドにmessageオプションを追加しましょう。

@Option(names = {"-m", "--message"})
private String message;

@Override
public void run() {
    System.out.println("Committing files in the staging area, how wonderful?");
    if (message != null) {
        System.out.println("The commit message is " + message);
    }
}

当然のことながら、 message オプションを指定すると、コマンドはコンソールにコミットメッセージを表示します。 この記事の後半では、ライブラリによって処理されるタイプと、他のタイプを処理する方法について説明します。

5.3. 複数の引数を持つオプション

しかし、実際の git commit コマンドで行われるように、コマンドで複数のメッセージを受け取るようにするにはどうすればよいでしょうか。 心配はいりません。フィールドを配列またはコレクションにしましょう。これでほぼ完了です。

@Option(names = {"-m", "--message"})
private String[] messages;

@Override
public void run() {
    System.out.println("Committing files in the staging area, how wonderful?");
    if (messages != null) {
        System.out.println("The commit message is");
        for (String message : messages) {
            System.out.println(message);
        }
    }
}

これで、messageオプションを複数回使用できるようになりました。

commit -m "My commit is great" -m "My commit is beautiful"

ただし、オプションを1回だけ指定し、正規表現区切り文字でさまざまなパラメーターを区切ることもできます。 したがって、@Optionアノテーションのsplitパラメーターを使用できます。

@Option(names = {"-m", "--message"}, split = ",")
private String[] messages;

これで、 -m「私のコミットは素晴らしい」、「私のコミットは美しい」を渡して、上記と同じ結果を得ることができます。

5.4. 必要なオプション

場合によっては、必要なオプションがある場合があります。 required 引数(デフォルトは false )を使用すると、次のことが可能になります。

@Option(names = {"-m", "--message"}, required = true)
private String[] messages;

現在、messageオプションを指定せずにcommitコマンドを呼び出すことはできません。 これを実行しようとすると、picocliはエラーを出力します。

Missing required option '--message=<messages>'
Usage: git commit -m=<messages> [-m=<messages>]...
  -m, --message=<messages>

6. 位置パラメータの管理

6.1. 位置パラメータをキャプチャする

それでは、 add コマンドはまだそれほど強力ではないため、ここで注目しましょう。 すべてのファイルを追加することしかできませんが、特定のファイルを追加したい場合はどうでしょうか。

これを行うために別のオプションを使用することもできますが、ここでのより良い選択は、位置パラメーターを使用することです。 実際、位置パラメーターは、特定の位置を占めるコマンド引数をキャプチャすることを目的としており、サブコマンドでもオプションでもありません。

この例では、これにより次のようなことが可能になります。

add file1 file2

位置パラメータをキャプチャするために、@Parametersアノテーションを使用します。

@Parameters
private List<Path> files;

@Override
public void run() {
    if (allFiles) {
        System.out.println("Adding all files to the staging area");
    }

    if (files != null) {
        files.forEach(path -> System.out.println("Adding " + path + " to the staging area"));
    }
}

これで、以前のコマンドは次のように出力します。

Adding file1 to the staging area
Adding file2 to the staging area

6.2. 位置パラメータのサブセットをキャプチャする

注釈のインデックスパラメータのおかげで、キャプチャする位置パラメータをよりきめ細かくすることができます。 インデックスはゼロベースです。 したがって、次のように定義すると、

@Parameters(index="2..*")

これにより、オプションまたはサブコマンドと一致しない引数が3番目から最後までキャプチャされます。

インデックスは、範囲または単一の数値のいずれかであり、単一の位置を表します。

7. 型変換についての一言

このチュートリアルの前半で見たように、picocliはそれ自体でいくつかの型変換を処理します。 たとえば、複数の値を配列またはコレクションにマップしますが、パスクラスを使用する場合のように、引数を特定のタイプにマップすることもできます。 ]addコマンド。

実際のところ、 picocli には、多数の事前処理されたタイプが付属しています。 つまり、自分で変換することを考えなくても、これらのタイプを直接使用できます。

ただし、コマンド引数を、すでに処理されているタイプ以外のタイプにマップする必要がある場合があります。 幸いなことに、これはITypeConverterインターフェイスとタイプをコンバーターに関連付けるCommandLine#registerConverterメソッドのおかげで可能です。

configサブコマンドをgitコマンドに追加したいが、ユーザーが存在しない構成要素を変更したくないと想像してみてください。 したがって、これらの要素を列挙型にマップすることにします。

public enum ConfigElement {
    USERNAME("user.name"),
    EMAIL("user.email");

    private final String value;

    ConfigElement(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }

    public static ConfigElement from(String value) {
        return Arrays.stream(values())
          .filter(element -> element.value.equals(value))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("The argument " 
          + value + " doesn't match any ConfigElement"));
    }
}

さらに、新しく作成した GitConfigCommand クラスに、2つの位置パラメーターを追加しましょう。

@Parameters(index = "0")
private ConfigElement element;

@Parameters(index = "1")
private String value;

@Override
public void run() {
    System.out.println("Setting " + element.value() + " to " + value);
}

このようにして、ユーザーが存在しない構成要素を変更できないようにします。

最後に、コンバーターを登録する必要があります。 すばらしいのは、Java 8以降を使用している場合、ITypeConverterインターフェイスを実装するクラスを作成する必要さえないことです。 ラムダまたはメソッド参照をregisterConverter()メソッドに渡すことができます:

CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.registerConverter(ConfigElement.class, ConfigElement::from);

commandLine.parseWithHandler(new RunLast(), args);

これは、 GitCommand main()メソッドで発生します。 便利なCommandLine.run()メソッドを手放す必要があることに注意してください。

未処理の構成要素とともに使用すると、コマンドはヘルプメッセージと、パラメーターをConfigElementに変換できなかったことを示す情報を表示します。

Invalid value for positional parameter at index 0 (<element>): 
cannot convert 'user.phone' to ConfigElement 
(java.lang.IllegalArgumentException: The argument user.phone doesn't match any ConfigElement)
Usage: git config <element> <value>
      <element>
      <value>

8. スプリングブーツとの統合

最後に、それらすべてをSpringifyする方法を見てみましょう!

実際、Spring Boot環境内で作業していて、コマンドラインプログラムでその恩恵を受けたいと思うかもしれません。 そのためには、CommandLineRunnerインターフェイスを実装するSpringBootApplicationを作成する必要があります

@SpringBootApplication
public class Application implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
    }
}

さらに、すべてのコマンドとサブコマンドにSpring @Componentアノテーションで注釈を付け、アプリケーションですべてを自動配線しましょう。

private GitCommand gitCommand;
private GitAddCommand addCommand;
private GitCommitCommand commitCommand;
private GitConfigCommand configCommand;

public Application(GitCommand gitCommand, GitAddCommand addCommand, 
  GitCommitCommand commitCommand, GitConfigCommand configCommand) {
    this.gitCommand = gitCommand;
    this.addCommand = addCommand;
    this.commitCommand = commitCommand;
    this.configCommand = configCommand;
}

すべてのサブコマンドを自動配線する必要があることに注意してください。 残念ながら、これは、現時点では、 picocli が(アノテーションを使用して)宣言的に宣言された場合、Springコンテキストからサブコマンドを取得できないためです。 したがって、プログラム的な方法で、自分で配線を行う必要があります。

@Override
public void run(String... args) {
    CommandLine commandLine = new CommandLine(gitCommand);
    commandLine.addSubcommand("add", addCommand);
    commandLine.addSubcommand("commit", commitCommand);
    commandLine.addSubcommand("config", configCommand);

    commandLine.parseWithHandler(new CommandLine.RunLast(), args);
}

そして今、私たちのコマンドラインプログラムはSpringコンポーネントを備えた魅力のように機能します。 したがって、いくつかのサービスクラスを作成してコマンドで使用し、Springに依存性注入を処理させることができます。

9. 結論

この記事では、picocliライブラリのいくつかの重要な機能を見てきました。 新しいコマンドを作成し、それにいくつかのサブコマンドを追加する方法を学びました。 オプションと位置パラメータを処理する多くの方法を見てきました。 さらに、コマンドを強く型付けするために独自の型変換器を実装する方法を学びました。 最後に、Spring Bootをコマンドに組み込む方法を見てきました。

もちろん、それについて発見することはもっとたくさんあります。 ライブラリは完全なドキュメントを提供します。

この記事の完全なコードについては、私たちのGitHubにあります。