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

More than 1 year has passed since last update.

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


参考