SpringBoot x Spring Shell で簡易バッチ処理を作る

エキサイト株式会社エンジニアの佐々木です。2021年エキサイトホーディングスアドベントカレンダー7日目を担当させていただきます。

以前、SpringBootで対話的インターフェースをSpring Shellで実装するで対話的なアプリケーションについては記載しており、今回は簡易的なバッチ処理の方を記載します。

はじめに

前回の対話的なアプリケーションのコードを再掲します。(一部修正していますが、メソッド名部分のみになります。)

@ShellComponent(value = "demo")
public class DemoShellController {

    @ShellMethod(value = "足し算をする", key = "addCalc" , group = "calc")
    public Integer add(int a, @Max(10) int b, @ShellOption(value = "--optional_c" , defaultValue = "0") int c){
        return a + b + c;
    }
}

こちらになりますが、引数を渡されたものを足し算する簡単なものになります。前回のままだと対話的なアプリケーションになってしまい、Cron等で動作させられないので、これを改造していきます。

SpringShell内の実装方法

SpringShellではどうやって対話的アプリケーションにしているかの実装をみていきます。

InteractiveShellApplicationRunner.java

@Component
@Order(InteractiveShellApplicationRunner.PRECEDENCE)
public class InteractiveShellApplicationRunner implements ApplicationRunner {

        // 省略

    @Override
    public void run(ApplicationArguments args) throws Exception {
        boolean interactive = isEnabled();
        if (interactive) {
            InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider);
            shell.run(inputProvider);
        }
    }

        // 省略
}

ApplicationRunnerのインターフェースを用いて実装されているのがわかります。@Orderを用いて、実行順序を制御しています。簡易バッチの方の処理は、ApplicationRunnerインタフェースを用いて、且つ@Orderで対話型アプリケーションよりも早く動かないといけません。

簡易バッチの実装

下記のような実装コードになります。

NonInteractiveRunner.java

@Component
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 1)    //  ① InteractiveShellApplicationRunnerより1つ優先順位を上げる
public class NonInteractiveRunner implements ApplicationRunner {

    private Shell shell;
    private ConfigurableEnvironment configurableEnvironment;

    public NonInteractiveRunner(Shell shell, ConfigurableEnvironment configurableEnvironment) {
        this.shell = shell;
        this.configurableEnvironment = configurableEnvironment;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        if (args.getNonOptionArgs().isEmpty() || args.getNonOptionArgs().stream().anyMatch(s -> s.startsWith("@"))) {
            // ②ここに入った場合は、通常の対話型アプリケーションを実行する
            return;
        }

        InteractiveShellApplicationRunner.disable((configurableEnvironment));  // ③ ここで対話型アプリケーションを無効にする
        final Object evaluate = shell.evaluate(() -> String.join(" ", args.getSourceArgs()));  // ④ここで簡易バッチアプリケーションを実行する
        if (evaluate != null) {
            System.out.println(evaluate);    // ⑤出力するものがあればここで出力する
        }

    }
}

対話型アプリケーションの機能も残したい為、引数がなかったら or 引数に@があれば対話型アプリケーションの動きをするようにしています。(※対話型アプリケーションでは@があると、ファイルに記載されているコマンドを対話型アプリケーションの中で展開する仕様になっています。)

それ以外の場合は、対話型アプリケーションを無効にし、渡された引数でメソッド等を呼び出しバッチとして処理するようなものにします。

 $ java -jar build/libs/batch-0.0.1-SNAPSHOT.jar addCalc 1 2 4

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.0)

2021-12-05 17:46:45.958  INFO 76623 --- [           main] com.example.batch.BatchApplication       : Starting BatchApplication using Java 11.0.13 on 61-17616.local with PID 76623 (/Users/kohei.sasaki/git/sample/batch/build/libs/batch-0.0.1-SNAPSHOT.jar started by kohei.sasaki in /Users/kohei.sasaki/git/sample/batch)
2021-12-05 17:46:45.963  INFO 76623 --- [           main] com.example.batch.BatchApplication       : No active profile set, falling back to default profiles: default
2021-12-05 17:46:46.911  INFO 76623 --- [           main] com.example.batch.BatchApplication       : Started BatchApplication in 6.416 seconds (JVM running for 6.881)

7

答えは7になっています。 これで、SpringShellで簡易的なバッチ処理を作ることができました。対話型アプリケーションもコマンドアプリケーションもできるので、お得な実装になっているかと思います。

おまけ

上記のログにでている、起動時のSpringBootのバナーが邪魔ですね。これはメインメソッド内で消せるのでその実装を行います。

BatchApplication.java

@SpringBootApplication
public class BatchApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(BatchApplication.class)
                .bannerMode(Banner.Mode.OFF)  // ここでバナーをオフにする
                .run(args);
    }
}

上記のように、bannerMode()OFFにすれば表示されません。これで邪魔ではなくなりました。 下記のように、起動してもバナーがでないようになります。下記が実行結果です。

$ ./gradlew assemble && java -jar build/libs/batch-0.0.1-SNAPSHOT.jar addCalc 1 2 4

2021-12-06 09:33:25.406  INFO 85570 --- [           main] com.example.batch.BatchApplication       : Starting BatchApplication using Java 11.0.13 on 61-17616.local with PID 85570 (/Users/kohei.sasaki/git/sample/batch/build/libs/batch-0.0.1-SNAPSHOT.jar started by kohei.sasaki in /Users/kohei.sasaki/git/sample/batch)
2021-12-06 09:33:25.411  INFO 85570 --- [           main] com.example.batch.BatchApplication       : No active profile set, falling back to default profiles: default
2021-12-06 09:33:26.228  INFO 85570 --- [           main] com.example.batch.BatchApplication       : Started BatchApplication in 6.197 seconds (JVM running for 6.6)
7

まとめ

重要データに関しては、SpringBatch等をつかった方が、動作履歴やレジューム(途中から再開)機能があるのでそちらを使ったほうがいいですが、ちょっとしたバッチ処理であれば使ってみるのもいいと思います。対話型のアプリケーションと簡易型のコマンドラインアプリケーションがついてくるので、お得かもしれません。

最後に

2021年エキサイトホーディングスアドベントカレンダー7日目でした。引き続きエキサイトホーディングスのアドベントカレンダーをお楽しみいただけると幸いです。 qiita.com

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

カジュアル面談はこちらになります! meety.net

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com