Springシェルを使ったCLI

1概要

簡単に言うと、Spring Shell project は、Springプログラミングモデルを使用してコマンドを処理し、フル機能のCLIを構築するための対話型シェルを提供します。

この記事では、その機能、主要なクラス、および注釈について説明し、いくつかのカスタムコマンドとカスタマイズを実装します。

2 Mavenの依存関係

まず、 spring-shell 依存関係を pom.xml に追加する必要があります。

<dependency>
    <groupId>org.springframework.shell</groupId>
    <artifactId>spring-shell</artifactId>
    <version>1.2.0.RELEASE</version>
</dependency>

この成果物の最新バージョンは、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.shell%22%20AND%20a%3A%22spring-shell%にあります。 22[ここ]

3シェルへのアクセス

私たちのアプリケーションでシェルにアクセスするには2つの主な方法があります。

最初の方法は、アプリケーションのエントリポイントでシェルをブートストラップし、ユーザーにコマンドを入力させることです。

public static void main(String[]args) throws IOException {
    Bootstrap.main(args);
}

2つ目は JLineShellComponent を取得して、プログラムでコマンドを実行することです。

Bootstrap bootstrap = new Bootstrap();
JLineShellComponent shell = bootstrap.getJLineShellComponent();
shell.executeCommand("help");

この記事の例に最も適しているので、最初のアプローチを使用しますが、ソースコードには2番目の形式を使用するテストケースがあります。

4コマンド

clear help exit など、シェルにはすでにいくつかの組み込みコマンドがあり、すべてのCLIの標準機能を提供します。

カスタムコマンドは、 CommandMarker インターフェースを実装するSpringコンポーネント内に @ CliCommand アノテーションでマークされたメソッドを追加することによって公開できます。

そのメソッドのすべての引数に @ CliOption アノテーションを付ける必要があります。これを怠ると、コマンドを実行しようとしたときにいくつかのエラーが発生します。

4.1. シェルにコマンドを追加する

まず、コマンドがどこにあるかをシェルに知らせる必要があります。このためには、ファイル META-INF/spring/spring-shell-plugin.xml がプロジェクトに存在する必要があります。そこで、Springのコンポーネントスキャン機能を使用できます。

<beans ... >
    <context:component-scan base-package="org.baeldung.shell.simple"/>
</beans>

コンポーネントがSpringによって登録およびインスタンス化されると、それらはシェルパーサーに登録され、それらの注釈が処理されます。

2つの簡単なコマンドを作成しましょう。1つはURLのコンテンツを取得して表示するためのもの、もう1つはファイルにコンテンツを保存するためのものです。

@Component
public class SimpleCLI implements CommandMarker {

    @CliCommand(value = { "web-get", "wg" })
    public String webGet(
      @CliOption(key = "url") String url) {
        return getContentsOfUrlAsString(url);
    }

    @CliCommand(value = { "web-save", "ws" })
    public String webSave(
      @CliOption(key = "url") String url,
      @CliOption(key = { "out", "file" }) String file) {
        String contents = getContentsOfUrlAsString(url);
        try (PrintWriter out = new PrintWriter(file)) {
            out.write(contents);
        }
        return "Done.";
    }
}

それぞれ @ CliCommand @ CliOption value key 属性に複数の文字列を渡すことができることに注意してください。これにより、同じ動作をするいくつかのコマンドと引数を公開することができます。

それでは、すべてが期待通りに機能しているかどうか確認しましょう。

spring-shell>web-get --url https://www.google.com
<!doctype html ...
spring-shell>web-save --url https://www.google.com --out contents.txt
Done.

4.2. コマンドの可用性

実行時にコマンドをシェルに公開する必要がある場合は、 boolean を返すメソッドに @ CliAvailabilityIndi​​cator アノテーションを使用して変更できます。

まず、 web-save コマンドの可用性を変更するためのメソッドを作成しましょう。

private boolean adminEnableExecuted = false;

@CliAvailabilityIndicator(value = "web-save")
public boolean isAdminEnabled() {
    return adminEnableExecuted;
}

それでは、 adminEnableExecuted 変数を変更するコマンドを作成しましょう。

@CliCommand(value = "admin-enable")
public String adminEnable() {
    adminEnableExecuted = true;
    return "Admin commands enabled.";
}

最後に確認しましょう。

spring-shell>web-save --url https://www.google.com --out contents.txt
Command 'web-save --url https://www.google.com --out contents.txt'
  was found but is not currently available
  (type 'help' then ENTER to learn about this command)
spring-shell>admin-enable
Admin commands enabled.
spring-shell>web-save --url https://www.google.com --out contents.txt
Done.

4.3. 必須の引数

デフォルトでは、すべてのコマンド引数はオプションです。ただし、 @ CliOption アノテーションの mandatory 属性を使用して、それらを必須にすることができます。

@CliOption(key = { "out", "file" }, mandatory = true)

これで、紹介しないとエラーになることをテストできます。

spring-shell>web-save --url https://www.google.com
You should specify option (--out) for this command

4.4. デフォルト引数

@ CliOption の空の key 値は、その引数をデフォルトにします。

そこで、シェルに導入された、名前付き引数の一部ではない値を受け取ります。

@CliOption(key = { "", "url" })

それでは、期待通りに動作することを確認しましょう。

spring-shell>web-get https://www.google.com
<!doctype html ...

4.5. ユーザーを支援する

@ CliCommand および @ CliOption アノテーションは help 属性を提供します。これを使用すると、組み込みの help コマンドを使用するとき、またはタブを押して自動補完を実行するときにユーザーをガイドできます。

カスタムヘルプメッセージを追加するために web-get を修正しましょう:

@CliCommand(
 //...
  help = "Displays the contents of an URL")
public String webGet(
  @CliOption(
   //...
    help = "URL whose contents will be displayed."
  ) String url) {
   //...
}

これで、ユーザーは私たちのコマンドが何をするのかを正確に知ることができます。

spring-shell>help web-get
Keyword:                    web-get
Keyword:                    wg
Description:                Displays the contents of a URL.
  Keyword:                  ** **  default ** **
  Keyword:                  url
    Help:                   URL whose contents will be displayed.
    Mandatory:              false
    Default if specified:   '____NULL____'
    Default if unspecified: '____NULL____'

**  web-get - Displays the contents of a URL.
**  wg - Displays the contents of a URL.

5カスタマイズ

BannerProvider PromptProvider 、および HistoryFileNameProvider の各インターフェイスを実装することによってシェルをカスタマイズする方法は3つあります。これらはすべてデフォルトの実装で提供されています。

また、プロバイダがこれらの実装よりも優先されるようにするには、 @ Order アノテーションを使用する必要があります。

カスタマイズを始めるための新しいバナーを作成しましょう。

@Component
@Order(Ordered.HIGHEST__PRECEDENCE)
public class SimpleBannerProvider extends DefaultBannerProvider {

    public String getBanner() {
        StringBuffer buf = new StringBuffer();
        buf.append("=======================================")
            .append(OsUtils.LINE__SEPARATOR);
        buf.append("**           Baeldung Shell             ** ")
            .append(OsUtils.LINE__SEPARATOR);
        buf.append("=======================================")
            .append(OsUtils.LINE__SEPARATOR);
        buf.append("Version:")
            .append(this.getVersion());
        return buf.toString();
    }

    public String getVersion() {
        return "1.0.1";
    }

    public String getWelcomeMessage() {
        return "Welcome to Baeldung CLI";
    }

    public String getProviderName() {
        return "Baeldung Banner";
    }
}

バージョン番号とウェルカムメッセージを変更することもできます。

では、プロンプトを変更しましょう。

@Component
@Order(Ordered.HIGHEST__PRECEDENCE)
public class SimplePromptProvider extends DefaultPromptProvider {

    public String getPrompt() {
        return "baeldung-shell";
    }

    public String getProviderName() {
        return "Baeldung Prompt";
    }
}

最後に、履歴ファイルの名前を変更しましょう。

@Component
@Order(Ordered.HIGHEST__PRECEDENCE)
public class SimpleHistoryFileNameProvider
  extends DefaultHistoryFileNameProvider {

    public String getHistoryFileName() {
        return "baeldung-shell.log";
    }

    public String getProviderName() {
        return "Baeldung History";
    }

}

履歴ファイルはシェルで実行されたすべてのコマンドを記録し、私たちのアプリケーションと一緒に置かれます。

すべてが整ったら、シェルを呼び出して動作を確認できます。

=======================================
**           Baeldung Shell             **
=======================================
Version:1.0.1
Welcome to Baeldung CLI
baeldung-shell>

6. コンバーター

これまでは、コマンドへの引数として単純型を使用してきました。

Integer Date Enum File などの一般的なタイプには、デフォルトのコンバーターが既に登録されています。

Converter インターフェースを実装することで、カスタムオブジェクトを受け取るためのコンバーターも追加できます。

String URL に変換できるコンバーターを作成しましょう。

@Component
public class SimpleURLConverter implements Converter<URL> {

    public URL convertFromText(
      String value, Class<?> requiredType, String optionContext) {
        return new URL(value);
    }

    public boolean getAllPossibleValues(
      List<Completion> completions,
      Class<?> requiredType,
      String existingData,
      String optionContext,
      MethodTarget target) {
        return false;
    }

    public boolean supports(Class<?> requiredType, String optionContext) {
        return URL.class.isAssignableFrom(requiredType);
    }
}

最後に、 web-get および web-save コマンドを変更しましょう。

public String webSave(... URL url) {
   //...
}

public String webSave(... URL url) {
   //...
}

ご想像のとおり、コマンドは同じように動作します。

7. 結論

この記事では、Spring Shellプロジェクトのコア機能について簡単に説明しました。私達は私達のコマンドを提供し、私達のプロバイダーとシェルをカスタマイズすることができました、私達は異なったランタイム条件に従ってコマンドの利用可能性を変え、そして単純なタイプコンバーターを作成しました。

この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/spring-all[Githubに掲載]にあります。