Dependency Management Plugin 使い方メモ

# Dependency Management Plugin とは
Spring Project で開発されている（と表現していい？） Gradle のプラグイン。
Maven の BOM と同等の仕組みを Gradle でも使えるようにするためのもの。

BOM の説明は [Gradle で BOM を使いたいときには Spring チームの出している Dependency management plugin を使うのがよさそう - なにか作る](http://create-something.hatenadiary.jp/entry/2015/05/08/063000) などを参照。

もともと Spring Boot 1.x の頃は Spring Boot プラグインの一部だったが、 Spring Boot が 2.x になったときに独立したプラグインとして分離された。 +
なので、 Spring Boot とは関係なく利用できる（でも、実際一番お世話になるのは Spring Boot を使うときだと思う）。

# 環境
**Gradle**

`5.4.1`

**Dependency Management Plugin**

`1.0.8.RELEASE`

# Hello World
```gradle:build.gradle
plugins {
    id "java"
    id "io.spring.dependency-management" version "1.0.8.RELEASE"
}

repositories {
    mavenCentral()
}

dependencyManagement {
    dependencies {
        dependency "org.apache.commons:commons-lang3:3.9"
    }
}

dependencies {
    implementation "org.apache.commons:commons-lang3"
}

sourceCompatibility = 11
targetCompatibility = 11

compileJava.options.encoding = "UTF-8"
```

- plugins DSL を使わない書き方は https://plugins.gradle.org/plugin/io.spring.dependency-management とかを参照。

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-lang3 -> 3.9
```

**説明**

```gradle:build.gradle
dependencyManagement {
    dependencies {
        dependency "org.apache.commons:commons-lang3:3.9"
    }
}
```

- `dependencyManagement` の `dependencies` で、依存ライブラリをバージョンまで宣言する

```gradle:build.gradle
dependencies {
    implementation "org.apache.commons:commons-lang3"
}
```

- すると、プロジェクトの通常の依存関係の定義からバージョンの記述を省略できるようになる
- この場合は、 `dependencyManagement` で宣言したバージョンがデフォルトで使用されるようになる

# 同じグループの複数のアーティファクトのバージョンを一括で宣言する
```gradle:build.gradle
...

dependencyManagement {
    dependencies {
        dependencySet(group: "org.apache.poi", version: "4.1.0") {
            entry "poi"
            entry "poi-ooxml"
        }
    }
}

dependencies {
    implementation "org.apache.poi:poi"
    implementation "org.apache.poi:poi-ooxml"
}

...
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.poi:poi -> 4.1.0
|    +--- commons-codec:commons-codec:1.12
|    +--- org.apache.commons:commons-collections4:4.3
|    \--- org.apache.commons:commons-math3:3.6.1
\--- org.apache.poi:poi-ooxml -> 4.1.0
     +--- org.apache.poi:poi:4.1.0 (*)
     +--- org.apache.poi:poi-ooxml-schemas:4.1.0
     |    \--- org.apache.xmlbeans:xmlbeans:3.1.0
     +--- org.apache.commons:commons-compress:1.18
     \--- com.github.virtuald:curvesapi:1.06
```

**説明**

```gradle:build.gradle
dependencyManagement {
    dependencies {
        dependencySet(group: "org.apache.poi", version: "4.1.0") {
            entry "poi"
            entry "poi-ooxml"
        }
    }
}
```

- `dependencySet` を使うと、同じグループの複数のアーティファクトのバージョンを一括で指定できる
    - グループは `org.apache.poi` で、アーティファクトは `poi` と `poi-ooxml`
    - バージョンに `4.1.0` を指定している

# 推移的な依存関係から特定のライブラリを除外する
```gradle:build.gradle
...

dependencyManagement {
    dependencies {
        dependency("org.apache.poi:poi:4.1.0") {
            exclude "commons-codec:commons-codec"
        }
    }
}

dependencies {
    implementation "org.apache.poi:poi"
}

...
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.poi:poi -> 4.1.0
     +--- org.apache.commons:commons-collections4:4.3
     \--- org.apache.commons:commons-math3:3.6.1
```

- 前節の依存関係のグラフと比較すると、 `commons-codec` が無くなっているのが分かる

**説明**

```gradle:build.gradle
dependencyManagement {
    dependencies {
        dependency("org.apache.poi:poi:4.1.0") {
            exclude "commons-codec:commons-codec"
        }
    }
}
```

- `exclude()` を使うことで、推移的な依存関係の中にある特定のライブラリを除外できる

## dependencySet で指定する場合
```gradle:build.gradle
...

dependencyManagement {
    dependencies {
        dependencySet(group: "org.apache.poi", version: "4.1.0") {
            entry("poi") {
                exclude "commons-codec:commons-codec"
            }
            entry "poi-ooxml"
        }
    }
}

dependencies {
    implementation "org.apache.poi:poi"
    implementation "org.apache.poi:poi-ooxml"
}

...
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.poi:poi -> 4.1.0
|    +--- org.apache.commons:commons-collections4:4.3
|    \--- org.apache.commons:commons-math3:3.6.1
\--- org.apache.poi:poi-ooxml -> 4.1.0
     +--- org.apache.poi:poi:4.1.0 (*)
     +--- org.apache.poi:poi-ooxml-schemas:4.1.0
     |    \--- org.apache.xmlbeans:xmlbeans:3.1.0
     +--- org.apache.commons:commons-compress:1.18
     \--- com.github.virtuald:curvesapi:1.06
```

# BOM を読み込む
```gradle:build.gradle
...

dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
    }
}

dependencies {
    implementation "org.apache.commons:commons-lang3"
}

...
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-lang3 -> 3.8.1
```

**説明**

```gradle:build.gradle
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
    }
}
```

- `imports` の `mavenBom` で、 Maven の BOM を読み込むことができる
- ここでは [Spring Boot 2.1.6.RELEASE の BOM](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.1.6.RELEASE) を読み込んでいる
- `2.1.6.RELEASE` の BOM で指定されている `commons-lang3` のバージョンは [3.8.1 なので](https://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-dependencies/pom.xml#L51)、解決されたバージョンも `3.8.1` になっている
- 複数の BOM をインポートすることもできる
    - その場合、同じアーティファクトのバージョンは最後にインポートした BOM の宣言が使用される

## BOM で宣言されたバージョンを上書きする
### バージョンプロパティを上書きする
```gradle:build.gradle
...
dependencyManagement {
    dependencies {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE") {
                bomProperty "commons-lang3.version", "3.9"
            }
        }
    }
}

dependencies {
    implementation "org.apache.commons:commons-lang3"
}
...
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-lang3 -> 3.9
```

**説明**

```build.gradle
            mavenBom("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE") {
                bomProperty "commons-lang3.version", "3.9"
            }
```

- Maven BOM 上でプロパティを使ってバージョンが宣言されている場合、そのプロパティの値を上書きすることでバージョンを変更できる
    - Spring Boot の BOM では、 `commons-lang3` のバージョンは `commons-lang3.version` というプロパティで[宣言されている](https://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-dependencies/pom.xml#L51)
- ただし、この方法は**Maven プラグインで pom.xml を出力しない場合**にのみ使用すべき
    - なぜなら、この方法だと出力される `pom.xml` にはバージョンを上書きしているという情報が反映されないため

`bomProperties(Map)` を使う書き方もある。

```gradle:bomProperties(Map)で指定する例
dependencyManagement {
    dependencies {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE") {
                bomProperties([
                    "commons-lang3.version": "3.9",
                    "commons-pool2.version": "2.6.1"
                ])
            }
        }
    }
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.commons:commons-lang3 -> 3.9
\--- org.apache.commons:commons-pool2 -> 2.6.1
```

拡張プロパティで宣言する方法もある。

```gradle:拡張プロパティで指定する例
ext["commons-lang3.version"] = "3.8"
ext["commons-pool2.version"] = "2.6.0"

dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
    }
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.commons:commons-lang3 -> 3.8
\--- org.apache.commons:commons-pool2 -> 2.6.0
```

### 依存管理を上書きする
```gradle:build.gradle
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
        dependency "org.apache.commons:commons-lang3:3.9"
    }
}

dependencies {
    implementation "org.apache.commons:commons-lang3"
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-lang3 -> 3.9
```

**説明**

```gradle:build.gradle
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
        dependency "org.apache.commons:commons-lang3:3.9"
    }
}
```

- `dependency` で宣言することで、 BOM で宣言されたバージョンを上書きできる
    - `dependency` で宣言されたバージョンは、 BOM で宣言されたものよりも優先される
    - `imports` より上で `dependency` を宣言しても結果は同じになる
- この方法なら、 Maven プラグインで出力される `pom.xml` にもバージョンを上書きする内容が反映される

依存関係を宣言するときに普通にバージョンを指定することで上書きすることもできる。

```gradle:依存関係の宣言でバージョンを指定する方法
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }

        dependency "org.apache.commons:commons-lang3:3.9"
        dependency "org.apache.commons:commons-pool2:2.6.1"
    }
}

dependencies {
    implementation "org.apache.commons:commons-lang3"       // バージョン指定しない場合
    implementation "org.apache.commons:commons-pool2:2.6.0" // バージョン指定した場合
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.commons:commons-lang3 -> 3.9
\--- org.apache.commons:commons-pool2:2.6.0
```

- `implementation` でバージョンを直接指定した場合は、そのバージョンが採用されている（`dependency` よりも優先されている）

もし、依存関係の宣言で直接指定したバージョンを無視したい場合は、 `overriddenByDependencies` オプションに `false` を設定する。

```gradle:直接バージョンが指定された場合を無視する
dependencyManagement {
    overriddenByDependencies = false
    
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }

        dependency "org.apache.commons:commons-lang3:3.9"
        dependency "org.apache.commons:commons-pool2:2.6.1"
    }
}

dependencies {
    implementation "org.apache.commons:commons-lang3"
    implementation "org.apache.commons:commons-pool2:2.6.0"
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.commons:commons-lang3 -> 3.9
\--- org.apache.commons:commons-pool2:2.6.0 -> 2.6.1
```

- 直接指定した `2.6.0` は無視されて、 `dependency` で指定した `2.6.1` が採用された

## 読み込んだ BOM のプロパティを参照する
```gradle:build.gradle
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
    }
}

task showBomProperty {
    doFirst {
        println "commons-lang3.version = ${dependencyManagement.importedProperties["commons-lang3.version"]}"
    }
}
```

```bash:実行結果
> gradle showBomProperty
...
commons-lang3.version = 3.8.1
```

**説明**

```gradle:build.gradle
task showBomProperty {
    doFirst {
        println "commons-lang3.version = ${dependencyManagement.importedProperties['commons-lang3.version']}"
    }
}
```

- 読み込んだ BOM のプロパティは、 `dependencyManagement.importedProperties` から取得できる

## Exclusions
まず、 Spring Boot の BOM を読み込まずに `commons-dbcp2` への依存を宣言してみる。

```gradle:build.gradle
plugins {
    id "java"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.apache.commons:commons-dbcp2:2.5.0"
}
...
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-dbcp2:2.5.0
     +--- org.apache.commons:commons-pool2:2.6.0
     \--- commons-logging:commons-logging:1.2
```

`commons-logging` への依存が含まれていることが分かる。

次に、 Spring Boot の BOM を読み込んで試してみる。

```gradle:build.gradle
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
    }
}

dependencies {
    implementation "org.apache.commons:commons-dbcp2"
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-dbcp2 -> 2.5.0
     \--- org.apache.commons:commons-pool2:2.6.0 -> 2.6.2
```

`commons-logging` への依存が無くなっていることが分かる。

これは、 Spring Boot の BOM で、 [commons-logging への依存が exclusion で指定されている](https://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-dependencies/pom.xml#L1481) ために起きている。

この挙動を止めたい（BOM で `exclusion` に指定されていても、依存対象として含めてほしい）場合は、 `applyMavenExclusions` に `false` を設定する。

```gradle:build.gradle
dependencyManagement {
    applyMavenExclusions = false
    
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
    }
}

dependencies {
    implementation "org.apache.commons:commons-dbcp2"
}
```

```bash:実行結果
> gradle dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- org.apache.commons:commons-dbcp2 -> 2.5.0
     +--- org.apache.commons:commons-pool2:2.6.0 -> 2.6.2
     \--- commons-logging:commons-logging:1.2
```

`commons-logging` への依存が復活した。

# configuration ごとに設定する
```gradle:build.gradle
configurations {
    hoge
    fuga
    piyo
}

dependencyManagement {
    dependencies {
        dependency "org.apache.commons:commons-lang3:3.9"
    }

    hoge {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.8"
        }
    }

    fuga {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.8.1"
        }
    }
}

dependencies {
    hoge "org.apache.commons:commons-lang3"
    fuga "org.apache.commons:commons-lang3"
    piyo "org.apache.commons:commons-lang3"
}
```

```bash:実行結果
> gradle dependencies --configuration hoge
...
hoge
\--- org.apache.commons:commons-lang3 -> 3.8

> gradle dependencies --configuration fuga
...
fuga
\--- org.apache.commons:commons-lang3 -> 3.8.1

> gradle dependencies --configuration piyo
...
piyo
\--- org.apache.commons:commons-lang3 -> 3.9
```

**説明**

```gradle:build.gradle
dependencyManagement {
    dependencies {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.9"
        }
    }

    hoge {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.8"
        }
    }

    fuga {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.8.1"
        }
    }
}
```

- `dependencyManagement` の直下に `configuration` 名でスクリプトブロックを挟むことで、 `configuration` ごとに依存バージョンを宣言できる
    - BOM のインポートも `configuration` ごとに可能

# 依存関係のバージョンを参照する
```gradle:build.gradle
dependencyManagement {
    dependencies {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
        }
        dependency "org.apache.commons:commons-pool2:2.6.0"
    }

    testImplementation {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.9"
            dependency "org.apache.commons:commons-pool2:2.6.1"
        }
    }
}

task showManagedVersions {
    doFirst {
        println """\
        |[default]
        |  commons-lang3 = ${dependencyManagement.managedVersions['org.apache.commons:commons-lang3']}
        |  commons-pool2 = ${dependencyManagement.managedVersions['org.apache.commons:commons-pool2']}
        |
        |[testImplementation]
        |  commons-lang3 = ${dependencyManagement.testImplementation.managedVersions['org.apache.commons:commons-lang3']}
        |  commons-pool2 = ${dependencyManagement.testImplementation.managedVersions['org.apache.commons:commons-pool2']}
        |""".stripMargin()
    }
}
```

```bash:実行結果
> gradle showManagedVersions
...
[default]
  commons-lang3 = 3.8.1
  commons-pool2 = 2.6.0

[testImplementation]
  commons-lang3 = 3.9
  commons-pool2 = 2.6.1
```

**説明**

```gradle:build.gradle
    doFirst {
        println """\
        |[default]
        |  commons-lang3 = ${dependencyManagement.managedVersions['org.apache.commons:commons-lang3']}
        |  commons-pool2 = ${dependencyManagement.managedVersions['org.apache.commons:commons-pool2']}
        |
        |[testImplementation]
        |  commons-lang3 = ${dependencyManagement.testImplementation.managedVersions['org.apache.commons:commons-lang3']}
        |  commons-pool2 = ${dependencyManagement.testImplementation.managedVersions['org.apache.commons:commons-pool2']}
        |""".stripMargin()
    }
```

- `dependencyManagement.managedVersions` から、最終的に解決された依存関係のバージョンを参照できる
- `dependencyManagement.<configuration>.managedVersions` とすれば、 `configuration` ごとの情報も参照できる

# マルチプロジェクトで使っている場合に注意したほうが良さそうなこと
BOM で宣言されているバージョンを上書きする方法はいくつかあるが、依存関係で直接指定する方法は**マルチプロジェクトでは用いないほうが良い**かもしれない。

## 問題の起こる例
次のような構成の Spring Boot アプリケーションを作っていたとする。

```text:プロジェクト構成
|-build.gradle
|-settings.gradle
|
|-common/
| |-src/main/java/
| | `-sample/common/
| |   `-CommonComponent.java
| `-build.gradle
|
`-boot/
  |-src/main/java/
  | `-sample/
  |   `-SampleBootApplication.java
  `-build.gradle
```

- `common` は複数のプロジェクトから参照されるイメージ（ここでは説明を簡単にするため `boot` プロジェクトだけから参照されている）
- `boot` は、 Spring Boot のプロジェクト

```java:CommonComponent.java
package sample.common;

import org.springframework.stereotype.Component;
import org.apache.commons.lang3.ObjectUtils;

@Component
public class CommonComponent {

    public void method(Object obj) {
        System.out.println(ObjectUtils.isEmpty(obj));
    }
}
```

- `commons-lang3` の `ObjectUtils.isEmpty()` を使って、受け取ったオブジェクトが空かどうかを出力している

```java:SampleBootApplication.java
package sample;

import sample.common.CommonComponent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SampleBootApplication {

    public static void main(String[] args) {
        try (ConfigurableApplicationContext context = SpringApplication.run(SampleBootApplication.class, args)) {
            CommonComponent common = context.getBean(CommonComponent.class);
            common.method("Hello World");
        }
    }
}
```

- こちらは Spring Boot を起動させるクラス
- 先程の `CommonComponent` を取り出して実行している

このマルチプロジェクトの各 `build.gradle` を、次のように記述したとする。

```gradle:/build.gradle
plugins {
    id "org.springframework.boot" version "2.1.6.RELEASE" apply false
    id "io.spring.dependency-management" version "1.0.8.RELEASE" apply false
}

subprojects {
    apply plugin: "java"
    apply plugin: "io.spring.dependency-management"

    repositories {
        mavenCentral()
    }

    dependencyManagement {
        dependencies {
            imports {
                mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
            }
        }
    }

    sourceCompatibility = 11
    targetCompatibility = 11

    compileJava.options.encoding = "UTF-8"
}
```

- 各プロジェクトで共通な設定を `subprojects` でまとめている
- Dependency Management Plugin を使って、 Spring Boot の BOM を読み込んでいる

```gradle:/common/build.gradle
dependencies {
    implementation "org.springframework:spring-context"
    implementation "org.apache.commons:commons-lang3:3.9"
}
```

- `@Component` を使うので、 `spring-context` の依存を宣言している
- また、 `ObjectUtils` を使うので `commons-lang3` の依存を宣言している
    - **ここで、 `commons-lang3` のバージョンには `3.9` を直接指定している**

```gradle:/boot/build.gradle
apply plugin: "org.springframework.boot"

dependencies {
    implementation project(":common")
    implementation "org.springframework.boot:spring-boot-starter"
}
```

- Spring Boot アプリになるので、 `spring-boot-starter` を依存関係に指定している
- `common` プロジェクトを参照するので、こちらも依存関係に指定している

この設定でアプリケーションを起動すると、次のようになる。

```text:実行結果
> gradle bootRun
...

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

...
2019-07-06 20:06:16.365  INFO 16428 --- [           main] sample.SampleBootApplication             : Started SampleBootApplication in 0.846 seconds (JVM running for 1.335)
Exception in thread "main" java.lang.NoSuchMethodError: org.apache.commons.lang3.ObjectUtils.isEmpty(Ljava/lang/Object;)Z
        at sample.common.CommonComponent.method(CommonComponent.java:10)
        at sample.SampleBootApplication.main(SampleBootApplication.java:14)

> Task :boot:bootRun FAILED

FAILURE: Build failed with an exception.
```

- `NoSuchMethodError` で落ちた

## 何が起こった？
各プロジェクトの依存関係を出力すると何が起こったかが分かる。

```bash
# common プロジェクトの依存関係
> gradle :common:dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.springframework:spring-context -> 5.1.8.RELEASE
|    ...
\--- org.apache.commons:commons-lang3:3.9

# boot プロジェクトの依存関係
> gradle :boot:dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- project :common
|    +--- org.springframework:spring-context -> 5.1.8.RELEASE
|    |    ...
|    \--- org.apache.commons:commons-lang3:3.9 -> 3.8.1
\--- org.springframework.boot:spring-boot-starter -> 2.1.6.RELEASE
     ...
```

- よく見ると、 `common` プロジェクトの `commons-lang3` は `3.9` になっているが、 `boot` プロジェクトから見たときは `3.8.1` になっている
- ちなみに、問題の起こった `ObjectUtils.isEmpty()` は `3.9` で[新規追加されたメソッド](https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/ObjectUtils.html#isEmpty-java.lang.Object-)になる
- `common` プロジェクトがコンパイルされるときは `3.9` を参照しているので問題なくコンパイルはできる
- しかし、 Spring Boot として起動するときは `boot` プロジェクトの `runtimeClasspath` が使用される
- この結果、起動時に利用される `commons-lang3` は `3.8.1` になり、当該メソッドがなくて実行時エラーになっていた

## なぜバージョンが異なるのか？
```gradle:/common/build.gradle
dependencies {
    implementation "org.springframework:spring-context"
    implementation "org.apache.commons:commons-lang3:3.9"
}
```

- `common` プロジェクトは、 `commons-lang3` のバージョンを直接指定しているので `3.9` になる

```gradle:/boot/build.gradle
apply plugin: "org.springframework.boot"

dependencies {
    implementation project(":common")
    implementation "org.springframework.boot:spring-boot-starter"
}
```

- 一方で、 `boot` プロジェクトは特に `commons-lang3` のバージョンを指定していない
- したがって、 Spring Boot の BOM で定義されている `3.8.1` が採用されることになる

## 修正方法
```gradle:/build.gradle
plugins {
    id "org.springframework.boot" version "2.1.6.RELEASE" apply false
    id "io.spring.dependency-management" version "1.0.8.RELEASE" apply false
}

subprojects {
    apply plugin: "java"
    apply plugin: "io.spring.dependency-management"

    repositories {
        mavenCentral()
    }

    dependencyManagement {
        dependencies {
            imports {
                mavenBom "org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE"
            }

            dependency "org.apache.commons:commons-lang3:3.9"
        }
    }

    ...
}
```

- `dependencyManagement` の中で `commons-lang3` のバージョンを指定するように変更
- これは `subprojects` 内での設定なので、 `common` と `boot` の両プロジェクトに適用される

```gradle:/common/build.gradle
dependencies {
    implementation "org.springframework:spring-context"
    implementation "org.apache.commons:commons-lang3"
}
```

- `common` の `build.gradle` では、バージョンの指定をやめるように変更

```bash:依存関係の確認
# common の依存関係
> gradle :common:dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.springframework:spring-context -> 5.1.8.RELEASE
|    ...
\--- org.apache.commons:commons-lang3 -> 3.9

# boot の依存関係
> gradle :boot:dependencies --configuration runtimeClasspath
...
runtimeClasspath - Runtime classpath of source set 'main'.
+--- project :common
|    ...
|    \--- org.apache.commons:commons-lang3 -> 3.9
\--- org.springframework.boot:spring-boot-starter -> 2.1.6.RELEASE
     ...
```

- `boot` から見た場合も `3.9` になった

```text:実行結果
> gradle bootRun
...
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

...

false
```

- 当然、実行してもエラーにはならない


# 参考
- [spring-gradle-plugins/dependency-management-plugin: A Gradle plugin that provides Maven-like dependency management functionality](https://github.com/spring-gradle-plugins/dependency-management-plugin)
- [Dependency Management Plugin](https://docs.spring.io/dependency-management-plugin/docs/current-SNAPSHOT/reference/html/)