これは、なにをしたくて書いたもの?
ふと、これをやってもふつうに動くのかな?と思ったので。
いや、動いたんですけど。
Spring BootでSpring Web MVCを使って作成したMavenプロジェクトと、それをUber JARにパッケージングするMavenプロジェクトを
別々にして、マルチモジュールプロジェクトにして試してみます。
いや、ふつうやらない気はしますけど。
環境
今回の環境は、こちら。
$ java -version openjdk version "11.0.4" 2019-07-16 OpenJDK Runtime Environment (build 11.0.4+11-post-Ubuntu-1ubuntu218.04.3) OpenJDK 64-Bit Server VM (build 11.0.4+11-post-Ubuntu-1ubuntu218.04.3, mixed mode, sharing) $ mvn -version Apache Maven 3.6.2 (40f52333136460af0dc0d7232c0dc0bcf0d9e117; 2019-08-28T00:06:16+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.4, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-65-generic", arch: "amd64", family: "unix"
準備
マルチモジュール構成のMavenプロジェクトを作成します。
親pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>unpack-jar</artifactId> <packaging>pom</packaging> <version>0.0.1</version> <modules> <module>app</module> <module>launcher</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> </project>
この配下に、appとlauncherという2つのMavenプロジェクトを作成していきます。
アプリケーションコード側
Spring Web MVCとFreeMarkerで作成することにしましょう。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>unpack-jar</artifactId> <groupId>org.littlewings</groupId> <version>0.0.1</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>app</artifactId> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.9.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> </dependencies> </project>
Controller。
app/src/main/java/org/littlewings/spring/boot/App.java
package org.littlewings.spring.boot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @SpringBootApplication @Controller public class App { public static void main(String... args) { SpringApplication.run(App.class, args); } @GetMapping("/hello") public String hello(Model model) { model.addAttribute("message", "World"); return "hello"; } }
FreeMarkerテンプレート。
app/src/main/resources/templates/hello.ftl
Hello ${message?html}!!
この中身なら、HTMLエスケープ要らなかった…。
静的ファイル。
app/src/main/resources/static/index.html
Hello FreeMarker
HTMLじゃないですけど。
IDEとかからmainメソッドを持つクラスを起動する分には、これでもふつうに使えます。
Uber JARを作成する
もうひとつのMavenプロジェクト側では、アプリケーションコード側を依存関係に加えて、Uber JARを作成します。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>unpack-jar</artifactId> <groupId>org.littlewings</groupId> <version>0.0.1</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>launcher</artifactId> <dependencies> <dependency> <groupId>org.littlewings</groupId> <artifactId>app</artifactId> <version>${project.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.1.9.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
こんな感じで。
パッケージング。
$ mvn package
すると、「main classがわからない」とエラーになるので
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.1.9.RELEASE:repackage (default) on project launcher: Execution default of goal org.springframework.boot:spring-boot-maven-plugin:2.1.9.RELEASE:repackage failed: Unable to find main class -> [Help 1]
「app」側に入っていた、mainClassを指定します。
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.1.9.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <mainClass>org.littlewings.spring.boot.App</mainClass> </configuration> </plugin>
今度は成功するので、パッケージングして起動。
$ mvn package $ java -jar launcher/target/launcher-0.0.1.jar
これで、問題なく動作します。
$ curl localhost:8080/hello Hello World!! $ curl localhost:8080/index.html Hello FreeMarker
実に、ふつうでした…。
まあ、こんな構成しないと思いますけど。
なお、通常だとSpring BootでメインのプロジェクトでUber JARにするとJARの中にこんな感じで入りますが
17 Thu Oct 17 00:25:38 JST 2019 BOOT-INF/classes/static/index.html 24 Thu Oct 17 00:25:38 JST 2019 BOOT-INF/classes/templates/hello.ftl 1410 Thu Oct 17 00:25:40 JST 2019 BOOT-INF/classes/org/littlewings/spring/boot/App.class
依存関係として突っ込むと、こんな感じで入ります。
3787 Wed Oct 16 15:31:40 JST 2019 BOOT-INF/lib/app-0.0.1.jar
BOOT-INF/classesな構成を守らないといけないのかな?と思ったところが、今回の確認をした理由だったりします。
requiresUnpack?
ところで、Spring Boot Maven Pluginを見ていて、repackageゴールに「requiresUnpack」なるものがあったので、もしかしてこのあたり
必要だったりするのかな?とかちょっと思ったりしていました。
Spring Boot Maven Plugin – Spring Boot
Spring Boot Maven Plugin – spring-boot:repackage
結論としては、不要だったのですが。
この設定、なにに使うかというと、指定したJARが「ファイルとして欲しい場合に使う」みたいです。
Extract Specific Libraries When an Executable Jar Runs
requiresUnpackに特定の依存関係を指定しておくと、実行時に「java.io.tmpdir」で指定した一時ディレクトリ内に取り出してくれます。
たとえばこういう設定をすると
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.1.9.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <mainClass>org.littlewings.spring.boot.App</mainClass> <requiresUnpack> <dependency> <groupId>org.littlewings</groupId> <artifactId>app</artifactId> </dependency> </requiresUnpack> </configuration> </plugin>
指定したJARが、Uber JARの実行時に一時ディレクトリ内に取り出されていることが確認できます。
$ find /tmp/launcher-0.0.1.jar-spring-boot-libs-9a5ae828-0009-47ca-b1fa-f26a9e906307 -type f /tmp/launcher-0.0.1.jar-spring-boot-libs-9a5ae828-0009-47ca-b1fa-f26a9e906307/app-0.0.1.jar
requiresUnpackを指定しなかった場合には、このような一時ディレクトリは作成されません。