CLOVER🍀

That was when it all began.

Spring Boot CLIで、CommandLineRunnerを使った簡単なアプリケーションを作る(ついでにUber JARにする)

Spring Boot CLIを使うと、Groovyスクリプトで簡単にSpring Bootアプリケーションを書くことができます。

http://d.hatena.ne.jp/Kazuhira/20160214/1455460595

まあ、小さなスクリプト程度で済むものであれば、これでもいいかなという気もしますね。

62. Using the CLI

Spring Boot CLIは、Groovyスクリプト中に書かれたクラスなどを見て、実行時の構成を自動的に決定します。例えば、
@Controller、@RestController、@EnableWebMvcがあると、Spring MVCが有効になります。
https://github.com/spring-projects/spring-boot/blob/v1.5.3.RELEASE/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringMvcCompilerAutoConfiguration.java

このあたりのパッケージを見ると、スクリプト中になにを書くと、なにが有効になるかは確認することができます。
https://github.com/spring-projects/spring-boot/tree/v1.5.3.RELEASE/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure

お試し的にちょっとしたSpring MVCのアプリケーションを書いたりする時には、けっこう便利です。

ところで、単純なコンソールアプリケーションを作るにはどうしたらいいのでしょうか?

Spring Batch用のものはありますが、ちょっとそこまで要らない…みたいな場合。
https://github.com/spring-projects/spring-boot/blob/v1.5.3.RELEASE/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBatchCompilerAutoConfiguration.java

で、どうしたらいいかですが、CommandLineRunnerを実装したクラスを作成して置いておくといいのではないでしょうかと。

試しに、jsoupを使った簡単なサンプルを書いてみましょう。

Spring Boot CLIのインストール

まずは、Spring Boot CLIをインストールします。sdkmanはインストール済みとします。

Home - SDKMAN! the Software Development Kit Manager

Spring Boot CLIのインストールは、こちらのコマンドで実行します。

$ sdk install springboot

これで、「spring」コマンドが使えるようになります。

$ spring --version
Spring CLI v1.5.3.RELEASE

スクリプトを書く

それでは、サンプルのスクリプトを書いてみます。
hello-jsoup.groovy

@Grab('org.jsoup:jsoup:1.10.2')
import org.jsoup.Jsoup

class Runner implements CommandLineRunner {
    @Override
    public void run(String... args) {
      println("===== ${args[0]} =====")

      def doc = Jsoup.connect('https://jsoup.org/').get()
      def h1 = doc.select('h1')
      assert h1.first().text() == 'jsoup: Java HTML Parser'
      println("get text => ${h1.first().text()}")
    }
}

Grapesでjsoupを依存関係に引き込み、それからCommandLineRunnerインターフェースを実装したクラスを作成しました。

あとは実行するだけです。コマンドライン引数を渡す場合は、「--」の後に指定します。

$ spring run hello-jsoup.groovy -- 'Hello World'

結果。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.3.RELEASE)

2017-06-04 21:09:40.210  INFO 24650 --- [       runner-0] o.s.boot.SpringApplication               : Starting application on ikaruga-ubuntu with PID 24650 (started by kazuhira in /home/kazuhira/study/groovy/clover/spring-boot-cli)
2017-06-04 21:09:40.215  INFO 24650 --- [       runner-0] o.s.boot.SpringApplication               : No active profile set, falling back to default profiles: default
2017-06-04 21:09:40.714  INFO 24650 --- [       runner-0] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1e7557c6: startup date [Sun Jun 04 21:09:40 JST 2017]; root of context hierarchy
2017-06-04 21:09:41.543  INFO 24650 --- [       runner-0] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
===== Hello World =====
get text => jsoup: Java HTML Parser
2017-06-04 21:09:42.939  INFO 24650 --- [       runner-0] o.s.boot.SpringApplication               : Started application in 3.182 seconds (JVM running for 4.996)
2017-06-04 21:09:42.944  INFO 24650 --- [       Thread-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1e7557c6: startup date [Sun Jun 04 21:09:40 JST 2017]; root of context hierarchy
2017-06-04 21:09:42.954  INFO 24650 --- [       Thread-3] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

それぞれ、コマンドライン引数とjsoupを動かした結果が確認できます。

===== Hello World =====
get text => jsoup: Java HTML Parser

簡単なアプリケーションは、これで作れそうですね。

これが動作するのは、SpringApplicationへの依存関係がなければ「spring-boot-starter」を依存関係に追加し、
CommandLineRunnerを含むいくつかのクラスをimportするからみたいですね。
https://github.com/spring-projects/spring-boot/blob/v1.5.3.RELEASE/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java#L40-L44

Uber JARにする

ついでに、Uber JARにして実行時にはjavaコマンドがあればよいようにしましょう。

「spring jar」コマンドで、GroovyスクリプトをJARファイルにできます。

$ spring jar hello-jsoup.jar hello-jsoup.groovy

JARファイルの中には、Spring FrameworkやSpring Boot、そして自分で追加したjsoupなどが入っています。

JARファイルは、javaコマンドで実行できます。

$ java -jar hello-jsoup.jar 'Hello World'

簡単なアプリケーションなら、これでJARファイルにして実行してもよいかも?

※一応、複数Groovyファイルの実行や、複数GroovyファイルをまとめてUber JARにすることも可能みたいです
Applications with multiple source files
Packaging your application

蛇足

こう見ると、「CommandLineRunner要らないのでは?」とか「そのまま書きたいことをスクリプトに書いておけば良くない?」とか
思うかもしれませんが、そうはいかないみたいです。

例えば、先ほどのスクリプトをこういう形に修正すると
hello-jsoup.groovy

@Grab('org.jsoup:jsoup:1.10.2')
import org.jsoup.Jsoup

println("===== ${args[0]} =====")

def doc = Jsoup.connect('https://jsoup.org/').get()
def h1 = doc.select('h1')
assert h1.first().text() == 'jsoup: Java HTML Parser'
println("get text => ${h1.first().text()}")

動作が変わって、実装した部分がキレイに無視されます。

あと、Closureを定義してCommandLineRunnerにキャストする方法もアウトです。

def runner = { args -> println(args) } as CommandLineRunner

あくまでクラスとしての定義が必要みたいです。