Spring Boot と一般ライブラリの折り合いのつけかた

# この資料について

[JJUG CCC 2018 Spring](http://www.java-users.jp/ccc2018spring/#/time-table) の表題セッションの発表資料を Qiita にて公開したものです (twitter: [#ccc_g4](https://twitter.com/search?q=%23ccc_g4))。

[![slide1.png](https://qiita-image-store.s3.amazonaws.com/0/52205/83f439f2-0794-18d3-7480-0893528ef3dc.png "Spring Boot と一般ライブラリの折り合いのつけかた")](https://speakerdeck.com/saiya_moebius/spring-boot-to-ban-raiburarifalsezhe-rihe-ifalsetukekata)

内容は上記のスライドとほぼ同じですので、見やすい方で見ていただければと思います。




<!--====================================
章: はじめに
====================================-->


# Spring Boot とそれ以外のライブラリ

[Spring Initializr](https://start.spring.io/) で Spring Boot 一式が動くプロジェクトは簡単に作れる。

しかし、実際の web アプリケーションでは追加でさまざまなライブラリを使いたくなる (世の中や社内のコード資産の活用のため)。

とりあえず `build.gradle`, `pom.xml` に dependency を追加すれば動くが...


# ライブラリを追加によって発生しがちな課題

DI や Configuration の課題:

1. `@Autowired` 等の DI 機能を活用できていない
	- ライブラリ側に `static` やリフレクションがあるとなりがち
2. `@Configuration` が膨らんでしまい初期構築やメンテが大変

プロジェクトの管理の課題:

3. `@Configuration` や `build.gradle`, `pom.xml` が複数プロジェクト間でコピペ
4. ライブラリのバージョンを管理しきれておらず、バージョン競合や class の競合による変な挙動や脆弱性に悩まされる


# 足回りの整備

アプリケーションの本質的なコードに集中するために...

1. DI, Spring らしい方法で各種ライブラリのオブジェクトを使う
2. ライブラリの設定を簡潔に制御する
3. プロジェクト間で共有できる足回りを共有する
4. ライブラリの脆弱性対応・バージョンアップを安全に行う

こういったストレスの小さい環境を整備するための `@Configuration` や `build.gradle`, `pom.xml` のベストプラクティスをご紹介。






<!--====================================
1章: 一般のライブラリを DI らしく使う
====================================-->


# 1. 一般のライブラリを DI らしく使う

DI, Spring らしい方法で各種ライブラリのオブジェクトを使うことで、テストしやすさや保守性を向上する。


## まずは DI する

```java
static final SentryClient SENTRY_CLIENT = ...;

SENTRY_CLIENT.sendEvent(...);
```

↓

```java
@Autowired SentryClient sentryClient; // DI 先

sentryClient.sendEvent(...);
```
```java
@Configuration
class SentryConfiguration {
   @Bean
   public SentryClient sentryClient(){
       return ...;  // DI で注入するオブジェクト
   }
}
```


## Constructor injection とテスト

外部ライブラリのクラスは mock 化したくなることが多い。

Constructor injection にしておけば mock を素直に注入できる。

```java
@Component
class テスト対象 {
    private SentryClient sentryClient;
    
    @Autowired  // フィールドでなくコンストラクタ引数に DI
    public テスト対象(SentryClient sentryClient){
        this.sentryClient = sentryClient;
    }
}
```
```java
テスト対象 target = new テスト対象(mock(SentryClient.class));
```

`Always use constructor based dependency injection in your beans` <small>- [How not to hate Spring in 2016 - spring.io](https://spring.io/blog/2015/11/29/how-not-to-hate-spring-in-2016) </small>


## 都度 new しないよう心がける

都度 new が必要ではないのに都度 new しているケース:

```java
void doSomething(){
  AmazonS3 s3client = new AmazonS3Client(...);
  s3client.putObject(...);
  // 公式のコード例をそのままコピペするとこうなる
}
```

こういったクラスが都度 new されがち:

- `ObjectMapper` <small>(Jackson の JSON &lt;-&gt; Object mapper)</small>
- `AmazonS3Client`

普通に DI すれば一元管理や mock 注入も可能な上、初期化のエラーをアプリケーション起動時に知ることができる。

ただし thread-safe であることは要確認 <small>(`SimpleDateFormat` などは NG)</small>。


## 都度 new が必要なものの DI

本当に都度 new する必要がある場合、Factory を DI するのがオススメ:

```java
@Configuration
class SomethingConfig {
  @Bean
  SomethingFactory somethingFactory(){ ... }
}

@Component
class AComponent {
  @Autowired
  AComponent(SomethingFactory somethingFactory){ ... }

  void doSomething(){
    Something something = somethingFactory.create();
    something.doSomething(...);
  }
}
```


## Prototype bean や BeanFactory の誤用

`@Scope(SCOPE_PROTOTYPE)` や BeanFactory は都度 new する意図に適さないので留意のこと。

```java
@Bean @Scope(SCOPE_PROTOTYPE)
HogeApiRequest request(){ return new ... }

@Component
class HogeApiService {
  @Autowired
  HogeApiRequest request  // 常に同じインスタンス！
  // 呼び出し元の全 Bean を漏れなく Prototype にすればよいが...
  // 手動で getBean(HogeApiRequest) する手もあるが...
}
```

Factory を DI する方が、小さいオーバーヘッドかつ、安全・ライフサイクルが明確になる。

<div style="line-height:85%;">

<small>Tips: Prototype Bean は Autowire 先に依存する Bean を生成する用途では有用。例えば [Autowire 先のクラス名を元に Logger を生成する](http://saiya-moebius.hatenablog.com/entry/2017/11/08/033932)など。</small>

</div>


## リクエストに依存する注入

コンストラクタやフィールドへの注入(Autowired)だけが DI ではない。

- `HandlerMethodArgumentResolver` を作れば controller の引数にオブジェクトを注入できる
  - 例えば、User Agent 情報を解析した結果のオブジェクトを controller 引数に入れることができる 
- `HandlerInterceptorAdapter` を作れば `ModelAndView` にオブジェクトを注入できる

リクエストやセッション内容に依存するオブジェクトは、上記の手法で注入することで DI の恩恵を受けつつもリクエストに閉じた注入が可能になる。

<!-- TODO: スライド枚数によっては、上記 2 つをそれぞれ詳細に追加説明入れる -->


## static やグローバル変数への対処

DI を前提としないライブラリによくある仕様:

- static メソッドとしての機能提供
- `System#getProperty`, `ServletContext#getAttribute` といったグローバル状態への依存
- `ThreadLocal` の状態の依存

これらも DI に寄せたい:
- テストなどでの注入のしやすさ
- オブジェクトの初期化・設定処理を一元化し保守性向上


## static method と DI の橋渡し

static method をラップするオブジェクトを Bean にする:

```java
@Component
class AccountService {  // これを DI で取得して使う

    public Account getCurrentAccount(){
        // 以下の static method はここ以外からは直接呼ばない
        return AccountUtil.getCurrentAccount();  
    }
}
```

これだけで、
- テスト時に mock/spy に差し替えるのが容易になる
- ログを出す・エラーハンドリングを変える、といった改変も 1 箇所で行える


## グローバル状態の初期化

Bean 初期化直前に `System#setProperty` 等してしまうのが手。

```java
@Configuration
class SentryConfiguration {
   @Bean
   public SentryClient sentryClient(
       // application.properties/yml の値を取得
       @Value("sentry.dsn") String dsn
   ){
       System.setProperty("sentry.dsn", dsn);
       return ...;
   }
}
```

<!-- `WebApplicationInitializer` な Bean を作れば `ServletContext` の初期化も可能。 -->


# DI らしく: グローバル状態と Bean の順序

Configuration の実行順は不定なので、順序を明示する必要あり。

```java
@Configuration
class SentryConfiguration {
   @Bean
   public SentryClient sentryClient(){
       System.setProperty("sentry.dsn", ...)
       return ...;
   }
}

@Configuration 
@AutoConfigureAfter(SentryConfiguration.class) // ← ここ
class MyApiClientConfig {
   public ApiClient apiClient(){
      // 以下が内部で "sentry.dsn" に依存しているとする
      return new ApiClient();  
   }
}
```


## ライブラリ側から new されるケース

ライブラリ側が以下のようなデザインになっているケースもある:

- ライブラリの初期化時に callback のクラス名を文字列で渡す
- ライブラリ側からそのクラスが `new` され呼び出される

`ServletFilter` (設定パラメタに文字列しか受け取れない)やプラグイン機構を提供するライブラリでありがち。

ライブラリ側から `new` されてしまったオブジェクトは DI 管理外になってしまい、その中で `@Autowired` などの DI 機能を利用できない。


## DI 管理下の Bean へ処理を移譲

DI 管理外のオブジェクトから DI 管理下の Bean へ処理を移譲する。

1. 処理の実体を Bean として実装
2. Bean への参照を `ThreadLocal` や `ServletContext` に保持
3. ライブラリ側から `new` されてしまったオブジェクトは上記から Bean の参照を取得
4. 処理を Bean に移譲
5. 移譲先の Bean では DI のフル機能が使えるので、DI 管理下の Component を自由に使える

( Spring MVC の `DelegatingFilterProxy` が実例 )


## Bean への処理の移譲 (概念コード)

```java
@Configuration
class LoginClientConfig {
  public LoginClient client(ImplBean impl){
    return new LoginClient("com.example.LoginCallback")
  }  
}

class LoginCallback {  // DI 管理外から new される
  public void login(String user){
      LoginCallbackImpl.currentInstance.get().login(user);
  }
}

@Component
class LoginCallbackImpl {
  ThreadLocal currentInstance;
  
  public ImplBean(...){ currentInstance.set(this); }
  public void login(String user){ ... }
}
```


## Bean への処理の移譲 (補足)

概念コードからは省略したが気をつけたほうが良い点:

- 初期化の順序
  - 移譲先 Bean を移譲元より先に作って ThreadLocal に入れないとならない
  - 先の例で `LoginClient` を生成するメソッドの引数に、使わないのに `ImplBean` を Autowired している背景
- `ThreadLocal` からのリーク
  - 昔ながらの Servlet 環境では ThreadLocal に入れたままにするとメモリリークで困ることがある
  - 初期化完了時に ThreadLocal をクリアしておくと良い


## Bean の Lifecycle の活用

`AutoCloseable`, `Closeable` や `DisposableBean` な Bean はアプリケーション終了時に自動で `close()` してもらえる。

しかし、それ以外のメソッド(`disconnect()` とか `shutdown()` とか...)も、`destroyMethod` として指定すればアプリケーション終了時に自動で呼び出してもらえ、クリーンなシャットダウンが可能になる。

```java
@Bean(destroyMethod = "shutdown")
public LegacyClient myClient(){ ... }
```

終了時処理のためだけに `AutoCloseable` な型でラップしたりする必要はない。


<!-- これより前のスライドで prototype の誤用についてすでに触れているので割愛

リクエストに依存する注入 (補足)

Bean を取得するたびに new してもらえる `prototype` スコープの Bean という機能もあるが、以下の理由であまりオススメできない:

- うっかり singleton Bean (普通の Bean) から Autowire するとダメ
  - Autowire 処理時に 1 回 new されるだけになってしまうため
- 1 リクエスト内部で何回も new できてしまうため、意図せずに状態の異なる複数オブジェクトを作ってしまいがち
  - リクエストに依存する情報である以上、1 リクエストでは 1 個だけ new したほうが良い

Controller 引数や Model へ注入すれば、リクエスト毎に 1 個のオブジェクトに限定できる。


-->

## まとめ

- Constructor injection でテストフレンドリーに
- 都度 new する必要がある場合は Factory を Bean にすると良い
  - Thread-safe ならばそもそも都度 new しなくても良い
- リクエスト毎に定まるオブジェクトならば Controller 引数や Model へ注入すると良い
- 一見すると DI と相性が悪いケースでも、一手間加えれば DI に出来ることが多い
  - 多くの場合はここまでに述べた手法が使えるはず
  - 諦めない心をもって臨もう





<!--====================================
2章: `@Configuration` の記述効率化
====================================-->

# 2. `@Configuration` の記述効率化

DI に寄せる結果として `@Configuration` が膨らでいく。

そのためライブラリの設定を簡潔に制御する。


## @Value で設定を受け取る例

```java
@Bean
HogeApiClient hogeApiClient(
   @Value("hoge.url") String url,
   @Value("hoge.apiKey") String apiKey,
   @Value("hoge.timeout") long timeout,
   // ...
){
   HogeApiClient client = new HogeApiClient();
   client.setUrl(url);
   client.setApiKey(apiKey);
   client.setTimeout(timeout);
   // ...
   return client;
}
```

問題なく動くが、設定項目数に比例して冗長になる。


## `@ConfigurationProperties`

Spring Boot の `@ConfigurationProperties` がオススメ:

```java
@Bean
@ConfigurationProperties(prefix = "hoge")
HogeApiClient hogeApiClient(){ 
  return new HogeApiClient();
}
// hoge. 以下の全プロパティを HogeApiClient の setter へ代入
```

設定対象のプロパティが増減しても、application.properties/yml を更新するだけで OK


## `@ConfigurationProperties` のカスタマイズ

getter, setter を override すればカスタマイズも可:

```java
@Validated
private class MyHogeApiClient extends HogeApiClient {
  
  @Override
  @NotEmpty @URL  // JSR-303 の validator を使える
  public String getUrl(){ return super.getUrl(); }

  @Override
  public void setUrl(String url){ /*任意の処理*/ }
}

@Bean
@ConfigurationProperties(prefix = "hoge")
HogeApiClient hogeApiClient(){ 
  return new MyHogeApiClient();
}
```


## Map をラップ

設定を `Map` で受け取ったり `set(String, String)` といったメソッドで受け取るライブラリもある。

`@ConfigurationProperties` 化すれば以下のメリットを得られる:

- 型安全かつバリデーションも可能
- 使っている設定・使っていない設定が明確になる

```java
@Component // DI でこのインスタンスを取得可能
@Validated @ConfigurationProperties(prefix = "hoge")
class HogeApiClientConfig {
  /** この map をライブラリ側に渡す */
  protected Map<String, String> map = new HashMap<>();
  
  @NotEmpty @URL
  public String getUrl(){ return map.get(...); }
  public void setUrl(String url){ map.put(...); }
}
```


<!-- 

Map のラップ with Kotlin

```java
@ConfigurationProperties(
  prefix = "m3.staff-openid",
  ignoreUnknownFields = false  // 設定ファイルの typo なども検知
)
class M3StaffOpenIdFilterConfig {

  @JvmField  // 余計な getter,setter を生成させない
  internal val map = mutableMapOf<String, String?>(
          // デフォルト値
          "openIdProvider" to "https://example.com/auth/",
          "urlPattern" to "/openid/callback"
  )

  // getter,setter 定義
  var openIdProvider: String by map  
  var urlPattern: String by map
  var callbackUrl: String? by map
}
```

-->



## Bean のコレクションでありがちなパターン

Bean に複数の Bean を与える必要がある場合にありがちなコード:

```java
@Bean
Something something(
  @Qualifier("subA") SubComponent subA,
  @Qualifier("subB") SubComponent subB,
  @Qualifier("subC") SubComponent subC
){
  Something something = new Something();
  something.add(subA);
  something.add(subB);
  something.add(subC);
  // ...
  return something;
}

@Bean SubComponent subA(){ ... }
@Bean SubComponent subB(){ ... }
@Bean SubComponent subC(){ ... }
```


## Bean を列挙する

Spring に Bean を列挙させる方が記述効率が良い: 

```java
@Bean
Something something(List<SubComponent> components){
  Something something = new Something();
  components.forEach(something::add);
  return something;
}

@Bean @Order(10) SubComponent subA(){ ... }
@Bean @Order(20) SubComponent subB(){ ... }
@Bean @Order(30) SubComponent subC(){ ... }
```

- `@Qualifier("Bean名")` の名前を間違えるリスクもない
- ハードコードでないため、別の Config から @Bean を追加することも容易

<small>`Filter` や `ConfigurerAdapter` などと同じ原理である。</small>


## 動的に Bean を生成

設定ファイルや DB 等の情報を元に Bean を動的に作ることも可能。

動的に作られる Bean の中で Autowire や AOP などを活用したり、それらの Bean を List などとして取得して呼び出すことができる。

動的 Bean 生成に使うものは大抵の場合これだけ:

- ImportBeanDefinitionRegistrar
- BeanDefinitionRegistry#registerBeanDefinition
- BeanDefinitionBuilder.genericBeanDefinition
  - setAutowireMode
  - addConstructorArgValue


## 動的な Bean の生成 擬似コード

この程度のコード量で Bean の定義を動的生成できる:

<span style="font-size:24px">

```java
/** このクラスを Configuration から @Import する */
class MyBeanRegistrar implements ImportBeanDefinitionRegistrar {
  void registerBeanDefinitions(..., BeanDefinitionRegistry registry){
    registry.registerBeanDefinition(
      "bean名",
      BeanDefinitionBuilder
        .genericBeanDefinition(Beanの実装型)
        // コンストラクタへの Autowire を有効にする
        .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR)
        // コンストラクタ引数を明示的に与える場合は以下
        .addConstructorArgValue(...)
    )
  }
}
```

</span>


## まとめ

- `@ConfigurationProperties` で DRY に書ける
  - Validation も可能
  - `Map` も型のあるオブジェクトでラップしよう
- Bean の列挙は `List<Beanの型>` と `@Order` がシンプル
  - Bean を追加しやすくなるため、拡張性も良くなる
- 動的な Bean 生成も意外と簡単
  - 外部ファイル等データに応じて Bean を制御する作り込み可能






<!--====================================
3章: プロジェクト間の足回りの脱コピペ
====================================-->

# 3. プロジェクト間の `@Configuration` の共有 (脱コピペ)

単一の web アプリケーションだけで完結せず、複数のアプリケーションを Spring Boot で作ることも少なくない <small>(特に microservice 志向の場合)</small>。

プロジェクト毎に個別に `@Configuration` を改善・メンテナンスするのはもったいない。

プロジェクトごとの差に対応しつつも、できるだけ共通化する。


## Configuration の共有

基本的には以下のアプローチになる:
- よく使う Configuration を入れた artifact を作っておく
- 上記の artifact に各アプリケーションから dependency を張る
- アプリケーション側で Configuration を @Import する

とはいえ、工夫しないと @Import がわかりにくくなりがち:

```java
@SpringBootApplication 
@Import(
  FooConfig.class,
  BarConfig.class,
  BarSubConfig.class,
  BazConfig.class,
  // ...
  // どの Config がどの Config に依存しているのか？
  // このアプリ固有の Config とアプリ共通の Config はどれか？
)
class MyApplication
```


## `@Import` の連鎖

`@Import(Configrationクラス.class)` を使えば、Configuration から他の Configuration を連鎖的に読み込むことができる。

```java
@Configuration
@Import(FooSubConfig.class)  // 連鎖的に読み込む
class FooConfig { ... }

@Configuration
class FooSubConfig { ... }
```

Import 元を読み込めばそれが依存する Config (Import 先)も読み込まれるので、アプリケーションは大元の Config のみを Import すれば良い。

アプリケーション側が Config の依存関係を知る必要がなくなる。


## Meta-Annotation と Import

Meta-Annotation でアノテーションをまとめてることも可能:

```java
/** いつものアノテーション一式をまとめたアノテーション
 * @SpringBootApplication の代わりにこれを使えばよい。*/
@SpringBootApplication
@ComponentScan(nameGenerator = ...)
@Import(
        MyBatisConfig.class,
        EmbeddedPostgresConfig.class,
        SentryConfig.class,
        ThymeleafConfig.class,
        TomcatConfig.class,
        // ...
)
annotation class M3SpringBootApplication
```

アプリケーション全体に影響するアノテーションと全 Config の Import をセットにしておくと便利

<!-- プロダクトによって使わない・使いたくない機能をどう避けるか？が次のスライドへ繋がる話 -->


## Config の切替

@Import の連鎖や Meta-Annotation によって Config をまとめて読み込めるが、常に全ての Config を有効にしたいとは限らない:

- 依存ライブラリがないと動かない Config は有効にしたくない
  - 例えば API 用のアプリでは Thymeleaf を依存に入れたくないので、Thymeleaf に依存する Config を走らせるとコケる
- 不要な Config は無効化してアプリケーションの起動を早くしたい
  - <small>どうしても起動が遅くなりがちなので...</small>

Config を使う側は楽に Import しつつも、読み込まれた Config をうまくオプトアウトできるようにするとより便利。


## Class 有無による切替

`@ConditionalOnClass` を使えば、class がロードされているかで切替られる:

```java
@Configuration
@ConditionalOnClass(
        TemplateMode.class, // thymeleaf
        ISpringTemplateEngine.class // thymeleaf-spring
)
@Import(LayoutConfig.class)
class ThymeleafConfig
```

条件にマッチしないときは @Import も行われなくなるので、一塊の Config をまとめて切り替えることができる (上の例の `ThymeleafConfig` 自体 + `LayoutConfig`)。


## プロパティによる切替

`@ConditionalOnProperty` を使えば property によって切替を実現できる:

```java
@Configuration
@ConditionalOnProperty(name = "m3.staff-openid.enabled")
class M3StaffOpenIdConfig { ... }
```

Config を使うかどうかを property で opt-in / opt-out 可能にできる。

それによって Config の利用側は Config のクラスを意識せずに property の書き換えだけで挙動を制御できる。


<!--

Config 共有: `ImportSelector`

... 例として、src/test で自動で追加ロードするなど ...

-->

## まとめ

- `@Import` は多段階に連鎖可能
  - 依存関係にある Config は自動で Import するようにしよう
- アノテーションの組み合わせは Meta-Annotation でまとめられる
- `@ConditionalOn...` で Config を切替可能
  - 必要な Config を一括で Import しつつ、プロジェクトごとに Config を取捨選択可能にしよう
  - Config 利用者は Config クラスの実体を意識する必要がない





<!--====================================
4章: ライブラリのバージョン管理
====================================-->

# 4. ライブラリの依存管理

依存ライブラリの管理はトラブルや大きな苦労の原因となりがち。

ライブラリ追加, バージョンアップ, 脆弱性対応 を安心して行うための仕込みを紹介。


## dependency のバージョン競合 (maven)

例えば以下の場合に、ライブラリ X の 1.0 が使われてしまう:

- ライブラリ A が X の 1.0 に依存
- ライブラリ B が X の 2.0 に依存

B の実行時に <small>`NoSuchMethodError`, `ClassNotFoundException`</small> などになる。

`mvn dependency:tree -Dverbose` に `omitted for conflict with` が出ているかを CI などで自動チェックするのがオススメ。 

conflict の対処:

- A の `<dependency>` に `<exclusion>` を指定することで B の依存バージョンに寄せる
- X に対して `<version>` 指定で `<dependency>` を明示する


## dependency のバージョン競合 (Gradle)

Gradle は競合時にデフォルトで新しい方のライブラリを使ってくれる。

とはいえ、機能が廃止されている・リネームされているといった場合には実行時のエラーになるので、Gradle でも競合は意識的に解消したほうが良い。

`resolutionStrategy` で `failOnVersionConflict()` しておくのがオススメ。

conflict の対処:

- `exclude` することで意識的に片方のバージョンに寄せる
- `resolutionStrategy` で特定バージョンを `force` する


## class, resource のダブり検知

異なる artifact 間に同じ class, resource が含まれてしまうことがある:

- commons-beanutils と commons-collections
- XML 処理ライブラリの Xerces の [さまざまな artifact](https://stackoverflow.com/q/11677572)
- fat jar の類とその依存先ライブラリ本体
- ロガーのアダプタ系全般 <small>(例: `commons-logging` と `jcl-over-slf4j`)</small>
- ライブラリの artifact 名のリネーム前とリネーム後

実行時にどちらがロードされるかに依存して挙動が変わってしまうため厄介。

`duplicate-finder-maven-plugin`, `gradle-duplicate-finder-plugin` によってビルド時に class の重複を検知するのがオススメ。

<small>依存ライブラリの多くが module に対応してくれれば起動時に検知できるが...</small>


## ライブラリの脆弱性チェック

`dependency-check-{maven, gradle}` プラグインが大変オススメ。

最新の脆弱性データベースの情報と dependency をビルド時に突き合わせられるので、以下の悩みから解放される:

- 定期的な点検をやろうと思っても忘れがち
- 日々増える脆弱性情報のキャッチアップ手段がない・大変
- 脆弱性の情報が来ても、自身のプロジェクトで使っているライブラリかどうかの判定が大変
  - 特に間接依存先のライブラリがモレがち

`failBuildOnCVSS=7` などすれば CVSS スコアが指定値以上の時にビルドエラーになるのでより検知しやすい。

<small>無視したい脆弱性は suppressionFile に指定することで無視可能。</small>


## バージョンの一括管理

<!-- BOM の使い方のイメージ -->

exclusion やバージョン指定なども含む、適切な version 指定をメンテナンスするのはそれなりに大変。

以下の手法を使って依存ライブラリのバージョン管理を一元化することができる:

- Maven: BOM ないし parent-pom に `<dependencyManagement>` を定義し各プロジェクトはそれを参照
- Gradle: Spring 提供の `dependency-management-plugin` の `mavenBom` を使って BOM を参照
  - [dependency-management-plugin についての Spring 公式 Blog 参照](https://spring.io/blog/2015/02/23/better-dependency-management-for-gradle)

BOM に定義している dependencyManagement しているもの全てに依存を貼ったプロジェクトを作っておき、CI で dpendency-check や duplicate-finder を実行することで各種検知もできる。


## まとめ

- `mvn dependency:tree` や `failOnVersionConflict()` でバージョン競合を確認
- `duplicate-finder` で class が衝突していないか自動チェック
- `dependency-check` でライブラリの脆弱性情報を自動チェック
- BOM や parent-pom で version 指定を一元管理




<!--====================================
章: まとめ, Appendix
====================================-->

# 総まとめ

spring-boot を使いつつ Java のライブラリ資産を活用する上での課題とその解決方法を紹介しました:

1. DI, Spring の仕組みに乗れる Configuration の書き方
    - 素直に DI できないオブジェクトの扱い方
2. 簡潔かつ拡張性のある設定の記述方法
    - <small>`@ConfigurationProperties`, `List<Bean型>`</small> で DRY に記述
    - Bean の動的生成
3. プロジェクト間での Configuration 共有のためのテクニック
    - `@Import` と `@ConditionalOn...` で選択的に共有
    - Meta-Annotation で annotation をまとめて管理
4. 依存ライブラリの管理の効率化
    - バージョンや class の衝突といった問題の検知・解決





<!--


Appendix


Appendix: Class の列挙・ロード方法

Java の標準機能ではできないが、やりたくなることが多い。

- ClassPathScanningCandidateComponentProvider (Spring)
  - Configuration 中では、`@ConditionalXXX` を実行しようとしてコケたりするので使いにくい
- com.google.common.reflect.ClassPath (guava)
  - spring-boot の実行可能 Jar (Uber jar)の構造を認識できない
  - guava の `ClassPath.ClassInfo#load()` は親側の ClassLoader を使ってしまう、class 名から Class をロードする部分は自前で `ClassLoader#loadClass` したほうが良い

- DevTools などと ClassLoader ツリーの関係


-->