使用Spring Boot创建简单的批处理(CLI)应用程序!

我想介绍一下如何在Spring Boot中创建一个简易的批处理应用程序(CLI应用程序)。在Spring中,有一个专门为批处理应用设计的框架叫做Spring Batch,你也可以在Spring Boot上使用它,但是对于创建一些简单的批处理任务可能会感到有些困难(因为它比较庞大)。如果遇到这种情况,我认为你可以考虑使用本文介绍的Spring Boot的ApplicationRunner机制。

核實版本

    Spring Boot 2.5.5

验证代码

    https://github.com/kazuki43zoo/spring-boot-cli-demo

创建一个实现ApplicationRunner接口的类。

在Spring Boot中,有一种机制可以在应用程序初始化过程结束后接收命令行参数并执行任意处理的功能。可以通过创建ApplicationRunner的实现类并将其注册到DI容器中来使用这种机制。

package com.example.demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component // DIコンテナの登録対象としてマーク
public class DemoApplicationRunner implements ApplicationRunner { // ApplicationRunnerを実装したクラスを作成

  @Override
  public void run(ApplicationArguments args) {
    // ... 任意の処理を実装
  }

}

接收命令行参数

可以通过ApplicationArguments来获取在命令行中指定的参数。在Java的main函数中,参数是一个字符串数组,但是通过使用ApplicationRunner,可以通过ApplicationArguments方便地处理带有名称的参数。

containsOption : 指定した名前の引数(–{引数名})が存在するかチェックする

getOptionValues : 指定した名前の引数値リストを取得する

getNonOptionArgs : 名前指定のない引数値リストを取得する

getOptionNames : 名前付き引数の引数名リストを取得する

getSourceArgs : main関数に渡された生の引数配列を取得する

@Override
public void run(ApplicationArguments args) {
  if (args.containsOption("h") || args.containsOption("help")) {
    System.out.println();
    System.out.println("[Usage]");
    System.out.println("  java -jar spring-boot-cli-demo.jar {calculation expressions}");
    System.out.println();
    System.out.println("[Command named arguments]");
    System.out.println("  --h (--help)");
    System.out.println("       print help");
    System.out.println("  --v (--version)");
    System.out.println("       print version");
    System.out.println();
    System.out.println("[Exit Codes]");
    System.out.println("  0 : Normal");
    System.out.println("  1 : Application error");
    System.out.println("  2 : Command arguments invalid");
    System.out.println("  3 : Calculation error");
    return;
  }
  if (args.containsOption("v") || args.containsOption("version")) {
    System.out.println();
    System.out.println("Version : " + getClass().getPackage().getImplementationVersion());
    return;
  }
  List<String> values = args.getNonOptionArgs();
  if (values.isEmpty()) {
    // 計算式の指定がない場合は警告ログを出力して処理を終了
    logger.warn("calculation expressions is required.");
    return;
  }
  String expressionString = String.join(" ", values);
  System.out.println("Expression : " + expressionString);
  System.out.println("Result     : " + new SpelExpressionParser().parseExpression(expressionString).getValue());
}

在上述的实施例中…

$ java -jar spring-boot-cli-demo.jar --h

假如如此,则会向控制台输出这个CLI应用程序的使用方式。

[Usage]
  java -jar spring-boot-cli-demo.jar {calculation expressions}

[Command named arguments]
  --h (--help)
       print help
  --v (--version)
       print version

[Exit Codes]
  0 : Normal
  1 : Application error
  2 : Command arguments invalid
  3 : Calculation error

再次…

$ java -jar spring-boot-cli-demo.jar --v

如果假设为真,则此CLI应用程序的版本将以以下方式输出到控制台。

Version : 0.0.1-SNAPSHOT

进一步来说……

$ java -jar spring-boot-cli-demo.jar 1 + 1

如果假设,那么通过SpEL评估接收到的字符串参数的值将作为执行结果输出到控制台。

Expression : 1 + 1
Result     : 2

根据处理内容定制退出代码

如果默认操作下,run方法正常结束(即未抛出异常)时,Java进程的结束代码将为“0”。
我认为这样并没有什么大问题,但有时可能希望将没有指定计算表达式作为参数时的结束代码设为非“0”的值(例如:2)!!在这种情况下,可以将实现了ExitCodeGenerator接口的组件注册到DI容器中,并通过在main函数中调用ExitCodeGenerator返回的值来实现System.exit。本文章将在ApplicationRunner的实现类中实现ExitCodeGenerator。

// ...
import org.springframework.boot.ExitCodeGenerator;
// ...

@Component
public class DemoApplicationRunner implements ApplicationRunner, ExitCodeGenerator { // ExitCodeGeneratorを実装

  private int exitCode; // 終了コードを保持しておくフィールドを用意

  @Override
  public int getExitCode() { // ExitCodeGeneratorのメソッドを実装
    return exitCode;
  }

  @Override
  public void run(ApplicationArguments args) {
    // ...
    List<String> values = args.getNonOptionArgs();
    if (values.isEmpty()) {
      // 計算式の指定がない場合は警告ログを出力して処理を終了
      this.exitCode = 2; // 終了コードを設定
      logger.warn("calculation expressions is required.");
      return;
    }
    // ...
  }

}
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootCliDemoApplication {

  public static void main(String[] args) {
    // SpringApplication.exitを呼び出してExitCodeGeneratorから終了コードを取得する
    int exitCode = SpringApplication.exit(SpringApplication.run(SpringBootCliDemoApplication.class, args));
    if (exitCode > 0) {
      // 取得した終了コードを指定してプロセスを終了する
      System.exit(exitCode);
    }
  }

}

如果不指定以下的计算式来执行…

$ java -jar spring-boot-cli-demo.jar

将结束代码变为”2″。

...
2021-09-26 20:20:53.723  WARN 34925 --- [           main] com.example.demo.DemoApplicationRunner   : calculation expressions is required.
$ echo $?
2

定制发生例外时的退出代码。

如果在run方法中抛出了异常,默认情况下Java进程的退出代码将变为”1″。
我认为这样没有什么大问题,但有时候我们可能希望根据异常的内容将退出代码设为非”1″的值(例如:计算处理错误设为”3″)!
在这种情况下,我们可以通过将实现了ExitCodeExceptionMapper接口的组件注册到DI容器中来实现这一目标。在本文中,我们将在实现了ApplicationRunner接口的类中实现ExitCodeExceptionMapper。

// ...
import org.springframework.boot.ExitCodeExceptionMapper;
// ...

@Component
public class DemoApplicationRunner implements ApplicationRunner, ExitCodeGenerator, ExitCodeExceptionMapper { // ExitCodeExceptionMapperを実装

  // ...

  @Override
  public int getExitCode(Throwable exception) {
    return exception.getCause() != null && exception.getCause() instanceof SpelEvaluationException ? 3 : 1;
  }

}

当在计算处理过程中遇到错误时执行以下操作…

$ java -jar spring-boot-cli-demo.jar 1 + a

结束代码将变为”3″。

...
Expression : 1 + b
2021-09-26 21:21:47.958 ERROR 35769 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Failed to execute ApplicationRunner
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:785) ~[spring-boot-2.5.5.jar!/:2.5.5]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:772) ~[spring-boot-2.5.5.jar!/:2.5.5]
...
$ echo $?
3

自定义横幅和日志

默认情况下,会输出横幅和INFO日志,因此控制台上会打印出Spring Boot的运行日志。

$ java -jar spring-boot-cli-demo.jar --v

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

2021-09-26 20:35:44.412  INFO 35012 --- [           main] c.e.demo.SpringBootCliDemoApplication    : Starting SpringBootCliDemoApplication v0.0.1-SNAPSHOT using Java 11.0.1 on xxx with PID 35012 (/Users/xxx/git-pub/spring-boot-cli-demo/target/spring-boot-cli-demo.jar started by xxx in /Users/xxx/git-pub/spring-boot-cli-demo/target)
2021-09-26 20:35:44.419  INFO 35012 --- [           main] c.e.demo.SpringBootCliDemoApplication    : No active profile set, falling back to default profiles: default
2021-09-26 20:35:45.482  INFO 35012 --- [           main] c.e.demo.SpringBootCliDemoApplication    : Started SpringBootCliDemoApplication in 1.793 seconds (JVM running for 2.53)

Version : 0.0.1-SNAPSHOT

如果您不希望提供任何多余的信息!那么您可以将横幅输出关闭,并将日志输出级别设置为警告级别,这样会更好。

logging.level.root=warn
spring.main.banner-mode=off

进行上述设置后,将不再显示任何不必要的横幅广告或日志。

$ java -jar spring-boot-cli-demo.jar --v

Version : 0.0.1-SNAPSHOT

可供参考的网站

    • https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.command-line-runner

 

    https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.application-exit
广告
将在 10 秒后关闭
bannerAds