Springのプロジェクトをマルチモジュール構成で作る
はじめに
Mavenではプロジェクトをマルチモジュール構成に構築することは可能ですが、Spring Frameworkでもその機能を利用することが可能です。
この辺の機能はあまり触ったことがなかったので、触ってみたいと思ったがのがこのブログのモチベーションです。
このブログでは以下のことをゴールとします。
- Spring Bootでのマルチモジュールプロジェクトの作り方を学ぶ
基本的にはここに書いてあることに従ってプロジェクトの作成を進めますが、気になったところは少し深ぼりしようかと思います。
また、ビルドツールはMavenを利用しようと思います。
プロジェクトを作成する
以下の二つのモジュールから成り立つWebアプリケーションを作成します
- library
- プロパティファイルから文字列を読み取り呼び出し側に返すサービスを含むライブラリ。applicationから利用される。
- application
- Springアプリケーションの本体。libraryを利用する。
動作環境
動作環境は以下のようになってます
$ uname -srvmpio Linux 5.3.0-46-generic #38~18.04.1-Ubuntu SMP Tue Mar 31 04:17:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ mvn -v Apache Maven 3.6.0 Maven home: /usr/share/maven Java version: 11.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.3.0-46-generic", arch: "amd64", family: "unix"
ルートプロジェクトを作成する。
まずはルートとなるプロジェクトを作成します。
私はIntelliJから作成しましたが、やり方は何でも良いと思います。
この際にsrcディレクトリは不要そうだったので削除しました。
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>dev.hirooka</groupId> <artifactId>multi-module</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>library</module> <module>application</module> </modules> </project>
ポイントは<packageing>
タグと<modules>
タグです。
Mevanでマルチモジュールなプロジェクトを作成する場合、<packageing>
ダグはpom
に指定する必要があるようです。
<modules>
タグでは、利用するモジュールを定義します。
まだ、applicationとlibraryは作成していないのでInteliJなどでは警告が出ていると思いますが、これから作成するので無視してしまって大丈夫です。
各モジュールを作成する
プロジェクトのルート直下に以下の2つのライブラリを作成します。
- library
- application
それぞれのプロジェクトを作成していきます。
プロジェクトの作成にはSpring Initializrを利用します。
libraryを作成する
libraryのプロジェクトを準備する
Spring Initializrでlibraryプロジェクトを作成します。
今回は以下のような設定を選択しました。
Project:Maven Project
Langage:Language
Group:dev.hirooka
Artifact:library
Name:library
Package name:dev.hirooka.library
Spring Boot バージョン:2.2.6
Java バージョン:11
ここで、Group(僕の例だとdev.hirooka
)とArtifact(library
)は後ほど使うことになります。値は任意のもので構いませんが、後ほどこれらの値が出てきた際は各環境での読み替えを願いします。
依存に関してはspring-boot-starter
とテスト関連のもののみ必要なので、ここでは追加では何もいれません。
ダウンロードしたzipを解答し、ルートプロジェクトの直下に置いてください。
また、Spring Initializrを用いた場合Mavenラッパーがプロジェクト内に含まれていると思いますが、これらはプロジェクトのルートに移して置いてください。
$ mv library/mvnw* library/.mvn .
ここまででプロジェクトの構造は以下のとおりになっていると思います。
$ tree . ├── application ├── library │ ├── HELP.md │ ├── pom.xml │ └── src │ ├── main │ │ ├── java │ │ │ └── dev │ │ │ └── hirooka │ │ │ └── library │ │ │ └── LibraryApplication.java │ │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── dev │ └── hirooka │ └── library │ └── LibraryApplicationTests.java ├── multi-module.iml ├── mvnw ├── mvnw.cmd └── pom.xml
libraryのpomを書き換える
Spring Initialzrで作成したアプリはデフォルトで実行可能Jarを作成するようになっていますが、Libraryはメインメソッドも必要なく、実行可能Jarを作る必要はありません。
ビルドシステムにそれを伝えるためにlibraryのpomを修正する必要があります。
具体的にはpomの以下の箇所を削除します。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
また、メインメソッドを持つLibraryApplication.java
も削除しておきましょう。
Serviceを作成する
libraryのプロジェクトを作成したところで、そのライブラリに属するサービスを作成します。
@Service @EnableConfigurationProperties(ServiceProperties.class) public class MyService { private final ServiceProperties serviceProperties; public MyService(ServiceProperties serviceProperties) { this.serviceProperties = serviceProperties; } public String message() { return this.serviceProperties.getMessage(); } }
このサービスでは、プロパティファイルから値を読み込んで、そのメッセージを返します。
値を読み込むための、ServiceProperties.class
も作成します。
@ConfigurationProperties("service") public class ServiceProperties { private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
サービスのテストを記述する。
@SpringBootTest
アノテーションを使えばモジュールのテストを書くことも可能です。
以下に作成したサービスのテストの例を示します。
package com.example.multimodule.service; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest("service.message=Hello") public class MyServiceTest { @Autowired private MyService myService; @Test public void contextLoads() { assertThat(myService.message()).isNotNull(); } @SpringBootApplication static class TestConfiguration { } }
@SpringBootTest
の引数として"service.message"
を渡しています。
ここで、モジュールを作成する際の注意点として、モジュール単位でapplication.properties
を作らないほうが良いです。これは、SpringBootではクラスパス配下の読み込む、application.properties
は1つだけであるため、このモジュールを利用する側に影響を与える可能性があるからです。
ただしtest/resource
配下のapplication.properties
はビルド後のjarの中に含まれないため、テストのために記述しておくことは可能です。
ここまでで、libraryの作成は完了です。
applicationを作成する
applicationのプロジェクトを作成する
libraryと同様にSpring Initializrを使ってaplicationのプロジェクトを作成します。
Project:Maven Project
Langage:Language
Group:dev.hirooka
Artifact:application
Name:application
Package name:dev.hirooka.application
Spring Boot バージョン:2.2.6
Java バージョン:11
依存にはwebとactuatorを追加しておきます。
Mavenラッパーはプロジェクトルートのものを使うため、削除しておきます。
$ rm -rf application/mvnw* application/.mvn
ここで、application側では、libraryを利用するためpomに依存を追加します。
<dependency> <groupId>dev.hirooka</groupId> <artifactId>library</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
applicationを作成する。
以下のようにアプリケーションを実装します。
@SpringBootApplication(scanBasePackages = "dev.hirooka") @RestController public class Application { private MyService myService; public Application(MyService myService) { this.myService = myService; } @GetMapping("/") public String hoge(){ return myService.message(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
ここでは、プロパティファイルから読み込んだ文字列を
よくあるコントローラーの実装なのですが、ひとつだけポイントがあります。
それは、@SpringBootApplication(scanBasePackages = "dev.hirooka")
でコンポーネントスキャンを対象とするパッケージを指定していることろです。
これは、libraryで実装されているserviceが存在しているパッケージはdev.hirooka.service
であり、@SpringBootApplicationが存在しているパッケージはdev.hirooka.application
なので、そのままではコンポーネントスキャンの対象とならないからです。
コンポーネントスキャンの対象にするためには以下の3つの方法があります。
- @Import(MyService.class)を使う
- @SpringBootApplication(scanBasePackageClasses={…})を活用する。
- @SpringBootApplication(scanBasePackages = "dev.hirooka")のようにパッケージ名を指定する
今回は三個目の方法を利用しました。
最後にapplication/src/main/resources/application.properties
にservice.message
を追加します。
service.message = hello, multi module
これで、applicationも完成です。
起動して動作を確認してみましょう。
$ ./mvnw install && ./mvnw spring-boot:run -pl application $ curl localhost:8080 hello, multi module
きちんとlibrayも使えて、application.propatiesが読み込めていることがわかります。
感想
Springのアプリケーションのmodule化を行なうためには、modulithsといったようなライブラリもあります。こんどはこっちを試してみたいです。