Help us understand the problem. What is going on with this article?

Spring Boot で CLI アプリをつくる

More than 3 years have passed since last update.

こう作ったら小綺麗に書けるってやりかたが、ある程度見えてきた気がするので、まとめておく。

環境

  • Java 1.8.0_91
  • Maven 3.3.9 (Maven wrapper)
  • Spring Boot 1.4.3.RELEASE
  • Apache Commons CLI 1.3.1
$ ./mvnw -version
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T01:41:47+09:00)
Maven home: /Users/yo1000/.m2/wrapper/dists/apache-maven-3.3.9-bin/2609u9g41na2l7ogackmif6fj2/apache-maven-3.3.9
Java version: 1.8.0_91, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.11.5", arch: "x86_64", family: "mac"

つかうもの

  • Spring Boot
  • Apache Commons CLI

依存関係の一部を抜粋。

pom.xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <commons-cli.version>1.3.1</commons-cli.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-cli</groupId>
        <artifactId>commons-cli</artifactId>
        <version>${commons-cli.version}</version>
    </dependency>
</dependencies>

やること

  • オプション付きパラメタの受け取り
  • オプション無しパラメタの受け取り
  • オプションに対するヘルプの表示
  • パイプされた標準入力の受け取り
  • エラーハンドリング

CLI アプリとして起動できるように構成

Spring Boot で、CLI アプリを作成する場合の基本中の基本。org.springframework.boot.CommandLineRunner インターフェースを実装したコンポーネントを作成します。

このようなクラスを作成しておくと、ターミナルからアプリケーションを起動した場合に、run メソッドが呼び出されるようになります。

@Component
public class DemoCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) {
        // 以下 ここに処理を実装していく
    }
}

パラメタの扱い

Apache Commons CLI を使用しているので、コマンドライン引数の扱いは非常に簡単です。以下の様なことができます。

コマンドライン引数を定義する
Options options = new Options();
options.addOption("?", "help", false, "Print this message.");
options.addOption("o", "output", true, "Output directory.");
コマンドライン引数をパースする
// void run(String... args) throws Exception {..}
CommandLine cl = new DefaultParser().parse(options, args);
オプションが指定されているかどうかを確認する
if (cl.hasOption("o")) {..}
オプション付きパラメタの受け取り
String output = cl.getOptionValue("o");
オプション無しパラメタの受け取り
// どちらもパラメタの内容自体は同じ
String[] params = cl.getArgs();
List<String> paramList = cl.getArgList();
ヘルプの出力
if (cl.hasOption("?")) {
    new HelpFormatter().printHelp("demo [-o <arg>]", options)
}

以上のように、パラメタはとても簡単に扱うことができます。

標準入力の扱い

CLI アプリであれば、標準入力から値を受け取って、パイプでつなげて処理できたりすると、スマートな感じがして良いのですが、こちらは Apache Commons CLI では処理できません。System.in を使って以下のようにすると、標準入力を扱えます。

標準入力から値の受け取り
// パイプされた標準入力を受け取ると、対話式 CLI に使用する System.console() は null を返す
if (System.console() == null) {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
        String stdin = reader.lines().collect(Collectors.joining());
    } catch (IOException e) {
        ..
    }
}

以上のように、パイプされた標準入力の扱いには一手間必要ですが、やり方さえわかってしまえば難しいことはないです。

エラーハンドリング

Spring を使っているので、AOP で例外をまとめて処理して、標準エラーに出力するのが簡単で良いです。

エラーハンドリング
@Aspect
@Component
public class ExceptionHandlerAdvice {
    @Around("execution(* org.springframework.boot.CommandLineRunner+.run(..))")
    public void handleException(ProceedingJoinPoint joinPoint) {
        try {
            joinPoint.proceed()
        } catch (Exception e) {
            System.err.println(e.getMessage())
        }
    }
}

その他

Spring Boot を使用しているため、CLI アプリとしては、バナー表示やフレームワークログの出力などが気になるので、これらを抑制しておきます。

src/resources/ 配下に、以下のファイルをそれぞれ配置することで、CLI アプリには望ましくないログ出力を抑制することができます。

application.yml
spring:
  main:
    banner-mode: log
logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>
    <root level="ERROR">
        <appender-ref ref="CONSOLE_APPENDER"/>
    </root>
</configuration>

起動方法

最後に、作成した CLI アプリの起動方法を確認しておきます。

実行可能 jar をまだ作成していない場合は、Maven から直接起動することになりますが、こちらの場合、パラメタの渡し方に一癖あります。

Javaからjarファイルを使って起動する場合
$ java -jar cli.jar -o oParam nonOptionParam
Mavenからソースを使って起動する場合
$ ./mvnw spring-boot:run -Drun.arguments=-o,oParam,nonOptionParam

以上で、Spring Boot を使用して、CLI アプリを作成する上で、必要になりそうな観点は一通り洗えたかと思います。

実際にこれらを取り入れた CLI アプリを作成したので、(Kotlin プロジェクトですが) こちらも参考にしてみてください。
https://github.com/yo1000/pdf2img

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away