社内の複数プロジェクトで、同じログ設定や外部API連携用のBeanを毎回コピペしていませんか。片方だけ直して片方は古いまま、なんてことも起きがちですよね。公式のStarterのように「依存を足すだけで有効化される」仕組みは、実は自分でも作れます。
この記事では、共通コードを再利用可能な自作Starterに昇格させる手順を、モジュール分割から設定メタデータの生成、別プロジェクトでの有効化検証まで一気通貫でやっていきます。想定は社内配布・内部リポジトリ向けで、Maven Centralへの公開は扱いません。
Starterを「使う側」の基礎は [[what-is-spring-boot-starter]] に、AutoConfigurationが動く内部の仕組みは [[spring-boot-autoconfiguration-mechanism-explained]] に譲ります。ここは 作る側 に集中します。
モジュールは2つに分ける
公式の慣習では、Starterは2つのモジュールに分けます。
xxx-spring-boot-autoconfigure… 自動構成の実体(クラスや条件)xxx-spring-boot-starter… 依存を束ねるだけの薄い集約モジュール
なぜ分けるのかというと、自動構成は要らないけれどライブラリ本体だけ使いたい、というケースに配慮するためです。autoconfigure側を直接依存すればBeanだけ使える、starter側を依存すれば全部入りで有効化される、という選択肢を利用者に残せます。
命名にも注意が必要です。spring-boot- で始まる名前はSpring公式の予約です。自作のものは acme-spring-boot-starter のように、自分のプレフィックスを頭に付けた xxx-spring-boot-starter 形式にしましょう。
ディレクトリはこんな構成になります。
acme-notify/
├── acme-notify-spring-boot-autoconfigure/ # 自動構成の実体
│ └── src/main/...
└── acme-notify-spring-boot-starter/ # 依存を束ねるだけ
└── pom.xml
autoconfigureモジュールの雛形
autoconfigure側のpom.xmlです。spring-boot-autoconfigure に依存し、メタデータ生成用の spring-boot-configuration-processor は optional で入れます。連携対象のライブラリも optional にしておくと、利用側のクラスパスに無いときに巻き込まずに済みます。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 連携対象のライブラリ(例)も optional に -->
<dependency>
<groupId>com.acme</groupId>
<artifactId>notify-client</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
starter側のpom.xmlは、autoconfigureモジュールと必要な実依存を推移的に引き込むだけです。
<dependencies>
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-notify-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.acme</groupId>
<artifactId>notify-client</artifactId>
</dependency>
</dependencies>
題材として、外部の通知APIを叩く NotifyClient を共通Beanとして提供する想定で進めます。
@AutoConfigurationで自動構成クラスを書く
自動構成クラスには @AutoConfiguration を付けます。これはSpring Boot 2.7で導入されたもので、内部的には @Configuration(proxyBeanMethods = false) 相当の自動構成専用アノテーションです。
条件アノテーションも一緒に付けてしまいましょう。
@AutoConfiguration
@ConditionalOnClass(NotifyClient.class)
@EnableConfigurationProperties(NotifyProperties.class)
public class NotifyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "acme.notify", name = "enabled", matchIfMissing = true)
public NotifyClient notifyClient(NotifyProperties props) {
return new NotifyClient(props.getEndpoint(), props.getApiKey());
}
}
条件の意味はそれぞれこうです。
@ConditionalOnClass… クラスパスにNotifyClientがある時だけ構成する@ConditionalOnMissingBean… 利用者が自分でBeanを定義していたらそちらを優先し、上書きしない@ConditionalOnProperty…acme.notify.enabled=falseで無効化できる(既定は有効)
この @ConditionalOnMissingBean が地味に大事です。付け忘れると、利用者がカスタムした NotifyClient を勝手に潰してしまいます。自作Starterは「邪魔をしない」設計が基本ですね。
AutoConfiguration.importsに登録する
@AutoConfiguration を付けただけでは、Spring Bootはそのクラスを見つけてくれません。登録ファイルが要ります。autoconfigureモジュールの src/main/resources 配下に、次のファイルを作ります。
src/main/resources/META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
中身は完全修飾クラス名を1行1クラスで書くだけです。
com.acme.notify.autoconfigure.NotifyAutoConfiguration
これはSpring Boot 2.7以降で、旧来の spring.factories の EnableAutoConfiguration キーに代わる仕組みです。古い記事だと META-INF/spring.factories に書く手順が出てきますが、今はこのimportsファイルが正解です。自動構成が効かないときの原因No.1が、このファイルの置き忘れやパス間違いなので、まずここを疑いましょう。
@ConfigurationPropertiesで設定をバインドする
外部設定は型安全に受け取りたいので、プロパティクラスを用意します。prefix は他ライブラリと衝突しないよう、ライブラリ名ベースで付けるのがコツです。
@ConfigurationProperties(prefix = "acme.notify")
public class NotifyProperties {
/** 通知APIのエンドポイントURL。 */
private String endpoint = "https://api.acme.example/notify";
/** 認証に使うAPIキー。 */
private String apiKey;
// getter / setter は省略
}
バインドの基本は [[spring-boot-properties-configuration-guide]]、@Validated を使った入力チェックは [[spring-boot-configuration-properties-validation-guide]] にまとめてあるので、そちらも合わせてどうぞ。
メタデータJSONでIDE補完を効かせる
さきほど optional で入れた spring-boot-configuration-processor が、ビルド時に META-INF/spring-configuration-metadata.json を自動生成します。これがあると、利用者のapplication.ymlで補完と説明が効くようになります。
生成される中身はこんな形です。
{
"properties": [
{
"name": "acme.notify.endpoint",
"type": "java.lang.String",
"description": "通知APIのエンドポイントURL。",
"defaultValue": "https://api.acme.example/notify"
}
]
}
注目したいのは description です。これはプロパティのフィールドに書いたJavadocコメントがそのまま反映されたものです。つまり丁寧なJavadocを書いておくと、そのまま利用者のIDEにヒントとして出ます。ドキュメントとコードが一致するので、書いておいて損はありません。
コンストラクタ引数など、プロセッサが拾えない項目を補足したいときは META-INF/additional-spring-configuration-metadata.json を手書きで足せます。
利用側のapplication.ymlはこう書けます。
acme:
notify:
enabled: true
endpoint: https://api.acme.example/notify
api-key: ${NOTIFY_API_KEY}
自動構成の順序が必要な場面
他の自動構成に依存する場合は、順序を指定します。たとえばDataSourceが構成された後に動きたいなら、after で後ろに回します。
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class NotifyAutoConfiguration {
// ...
}
逆に他の構成より先に動きたいなら before を使います。順序を誤ると、依存先のBeanがまだ無い状態で初期化が走って失敗します。とはいえ実務で必要になる場面は限られるので、最初は「他の構成に依存するときだけ最小限に指定する」くらいで十分です。
別プロジェクトで有効化を検証する
最後に、本当に依存追加だけで効くか確認しましょう。まずautoconfigureとstarterをローカルにインストールします。
mvn install
社内の内部リポジトリに上げるなら mvn deploy ですが、動作確認だけならローカルの mvn install で十分です。検証用プロジェクトのpom.xmlには、starterモジュールだけを追加します。
<dependency>
<groupId>com.acme</groupId>
<artifactId>acme-notify-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
これだけで NotifyClient がインジェクションできれば成功です。適用状況を細かく見たいときは、--debug を付けて起動するとConditions Evaluation Reportが出ます。
java -jar app.jar --debug
レポートの Positive matches に NotifyAutoConfiguration が出ていれば有効化されています。もし出ていなければ、次の3点を上から順に確認しましょう。
AutoConfiguration.importsにクラス名を書いたか、パスは正しいか@ConditionalOnClassの対象クラスがクラスパスにあるか@ConditionalOnPropertyや prefix の綴りが合っているか
まとめ
自作Starterの流れを振り返ると、2モジュール分割 → @AutoConfiguration → imports登録 → 条件付き構成 → プロパティ → メタデータ生成 → 検証、という一本道でした。一度型ができれば、他の共通機能にも同じパターンで横展開できます。
ポイントは @ConditionalOnMissingBean で利用者の上書きを尊重し、prefixを衝突しにくく設計して「邪魔をしないStarter」にすることです。コピペ運用から卒業すると、共通コードの修正が1か所で済み、プロジェクト間のばらつきも消えます。社内基盤の第一歩として、まずは一番よくコピペしている設定から昇格させてみてください。