Java
gradle

Gradle の compile, api, implementation とかについて

Gradle の dependencies で指定する compile, api, implementation についての勉強メモ。

compile は非推奨

Gradle 3.4 で Java Library Plugin追加されたことで、 dependenciescompile を使用することは非推奨となってたらしい(runtime, testCompile, testRuntime も)。

4.7 の Java Plugin の説明では、ガッツリ Deprecated と書かれている。
4.6 のドキュメントだと Deprecated って書いてないけど、明確に非推奨となったのは最近?)

代わりに implementationapi を使用することが推奨されている。

compile と implementation の違い

compile の場合

実装

プロジェクト構成
|-settings.gradle
|-build.gradle
|
|-foo/
|  |-build.gradle
|  `-src/main/java/foo/
|    `-Foo.java
|
`-bar/
   |-build.gradle
   `-src/main/java/bar/
     `-Bar.java

/settings.gradle
include 'foo', 'bar'
/build.gradle
subprojects {
    apply plugin: 'java'

    sourceCompatibility = 10
    targetCompatibility = 10

    compileJava.options.encoding = 'UTF-8'

    repositories {
        mavenCentral()
    }
}

/bar/build.gradle
dependencies {
    compile 'org.apache.commons:commons-lang3:3.7'
}
Bar.java
package bar;

import org.apache.commons.lang3.RandomStringUtils;

public class Bar {

    public void hello() {
        System.out.println("Bar: " + RandomStringUtils.random(10, "0123456789"));
    }
}

/foo/build.gradle
apply plugin: 'application'

mainClassName = 'foo.Foo'

dependencies {
    compile project(':bar')
}
Foo.java
package foo;

import bar.Bar;
import org.apache.commons.lang3.RandomStringUtils;

public class Foo {

    public static void main(String... args) {
        new Bar().hello();
        System.out.println("Foo: " + RandomStringUtils.random(10, "0123456789"));
    }
}
  • foo, bar という2つのサブプロジェクトから成る
  • barcommons-lang3 に依存している
  • foobar プロジェクトに依存している
  • それぞれのプロジェクトで commons-lang3 を利用している

実行結果

> gradle :foo:run
Bar: 3803159716
Foo: 6423224304

implementation を使った場合

実装

/bar/build.gradle
dependencies {
    implementation 'org.apache.commons:commons-lang3:3.7'
}
/foo/build.gradle
apply plugin: 'application'

mainClassName = 'foo.Foo'

dependencies {
    implementation project(':bar')
}

実行結果

> gradle :foo:run

...\foo\src\main\java\foo\Foo.java:4: エラー: パッケージorg.apache.commons.lang3は存在しません
import org.apache.commons.lang3.RandomStringUtils;
                               ^
...\foo\src\main\java\foo\Foo.java:10: エラー: シンボルを見つけられません
        System.out.println("Foo: " + RandomStringUtils.random(10, "0123456789"));
                                     ^
  シンボル:   変数 RandomStringUtils
  場所: クラス Foo
エラー2個

...

説明

依存の関係
# compile
[foo] --compile--> [bar] --compile--> [commons-lang3]

  [foo] - ok -> [bar] - ok -> [commons-lang3]
    |                               ^
    |                               |
    +------------ ok ---------------+

# implementation
[foo] --implementation--> [bar] --implementation--> [commons-lang3]

  [foo] - ok -> [bar] - ok -> [commons-lang3]
    |                               x
    |                               |
    +------------ ng ---------------+
  • compile で指定した依存関係は伝播する
    • bar プロジェクトで commons-lang3compile で指定した場合、 foo プロジェクトで bar プロジェクトを依存関係に指定すると、 foo プロジェクトは commons-lang3 にも依存するようになる
  • implementation で指定した依存関係は伝播しない
    • bar プロジェクトで commons-lang3implementation で指定した場合、 foo プロジェクトで bar プロジェクトを依存関係に指定しても、 foo プロジェクトは commons-lang3 に依存しない(foo プロジェクトは commons-lang3 を使用できない)

依存関係を伝播させる

実装

/bar/build.gradle
apply plugin: 'java-library'

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}

実行結果

$ gradle :foo:run

Bar: 7783742303
Foo: 6741510207

説明

/bar/build.gradle
apply plugin: 'java-library'

dependencies {
    api 'org.apache.commons:commons-lang3:3.7'
}
  • compile のように依存関係を伝播させるには、 api で依存関係を指定する
    • api は Java Library Plugin を追加することで利用できるようになる
    • Java Library Plugin を使うには、 java-library でプラグインを追加する

メリット

公式ドキュメントでは、 compile ではなく implementation を使うことについて、次のようなメリットがあると説明している。

  1. コンパイル時に依存関係が利用側のどこにも漏れることがない。
    従って、意図しない推移的な依存関係が発生することがない。
  2. 取り除かれたクラスパスによってコンパイルがより早くなる。
  3. implementation で指定した依存対象が変更されても、利用する側はリコンパイルの必要がない。
  4. 新しい Maven プラグインと合わせて使うと、コンパイル時に必要になるライブラリと実行時に必要になるライブラリを明確に分けた POM ファイルを生成するようになり、綺麗な公開ができる(正直、 Maven プラグイン使ってないのでよくわかってない)

要するに、 compile を使うと次のような問題があった。

  • 依存関係が全て推移的に伝播していくため、依存関係が不必要に拡大してしまっていた
    • 内部 API のような外部に漏らしたくない依存関係まで広がっていた

これを implementation で定義することで、依存関係の不必要な拡大を防ぎ、本当に必要な依存関係だけを api で推移させられるようになる。

また、 implementation が依存関係を伝播させなくなることで、リコンパイルの頻度も減らせられるようになるメリットがある。

implementationapi の使い分け

以下は個人的な見解。

  • 基本は implementation で宣言する
  • どうしても利用側に伝播させないといけない場合だけ api で利用側に公開する
    • 可能なら自作 API でラップすることで、使用しているライブラリへの直接の依存を無くせれたらベストだと思う
      • ライブラリの差し替えが容易になる
      • 自作 API で利用方法を制限することで、意図しない使われ方・間違った利用方法を減らせられる
    • ただ、自作 API でのラップが大変で費用対効果が薄いなら api で公開するのもありかと思う

まとめ

configuration 依存関係の伝播 定義されているプラグイン
compile する Java Plugin
implementation しない Java Plugin
api する Java Library Plugin

参考