fat JAR
fat JARとは、アプリケーションの実行に必要なクラスやリソースを1つのJARファイルにまとめたもの。依存ライブラリを全て内包することで、外部依存なしに実行できるようになる。
通常のJARファイルは自身のクラスのみを含み、実行時に依存ライブラリを別途クラスパスに追加する必要がある。一方でfat JARは全ての依存関係を含むため、単独で実行可能になる。
gradleup.shadow
Shadowは、Gradleプロジェクトでfat JARを作成するためのプラグイン。元々はcom.github.johnrengelman.shadow
というpackageで開発されていたが、現在はcom.gradleup.shadow
としてGradleUpに移管されている。
以下のように利用する。
plugins {
id("com.gradleup.shadow") version "8.3.6"
}
ShadowプラグインはshadowJar
というタスクを追加し、指定した依存関係を含むfat JARを生成する。
使いどころ
Gradleプラグインはそのプラグインを適用しているプロジェクトでGradleタスクを実行したタイミングで依存関係を解決する。
このため、Gradleプラグインと利用するプロジェクトで同じライブラリを利用している場合、依存関係が競合するケースがある。
これにより、ビルドが失敗したり、一貫性のない振る舞いの原因となる。
Shadowプラグインを使ってfat JARを作成すると、プラグインの依存関係をプラグインJAR内に含めることができる。また、リロケーションによって、これらの依存関係のパッケージ名を変更できる。
リロケーションとは
リロケーションとは、JAR内のクラスのパッケージ名を変更することで、クラスローダーからは異なるクラスとして認識されるようにすること。
例えば、com.google.gson
パッケージをcom.my.plugin.internal.gson
に変更することで、プロジェクトが使っている本来のGsonライブラリと競合しなくなる。
tasks.shadowJar {
relocate("com.google.gson", "com.my.plugin.internal.gson")
}
自動リロケーション
個別のパッケージごとにリロケーション設定を書くのは面倒だが、ShadowプラグインはenableRelocation
をtrueにすることで自動でリロケーションする設定にすることもできる。
tasks.shadowJar {
enableRelocation = true
relocatePrefix = "com.my.plugin.internal"
}
この設定により、プラグインの全ての依存ライブラリが自動的にrelocatePrefix
で指定した名前空間の下にリロケーションされる。例えばcom.google.gson
はcom.my.plugin.internal.com.google.gson
になる。
Java標準ライブラリなど、リロケーションすべきでないパッケージは自動的に除外される。
Gradleプラグイン公開時にfat Jarを利用する
Gradleプラグインを開発して公開する際、com.gradle.plugin-publish
プラグインを組み合わせることで、Shadowと連携してfat JARを簡単に公開できる。
- https://gradleup.com/shadow/gradle-plugins/
- https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#shadow_dependencies
plugins {
id("java-gradle-plugin")
id("com.gradle.plugin-publish") version "1.3.1"
id("com.gradleup.shadow") version "8.3.6"
}
tasks.shadowJar {
enableRelocation = true
relocatePrefix = "com.my.plugin.internal"
archiveClassifier.set("")
}
gradlePlugin {
plugins {
create("myPlugin") {
id = "com.my.plugin"
displayName = "My Plugin"
description = "A useful gradle plugin"
implementationClass = "com.my.plugin.MyPlugin"
}
}
}
JARのサフィックスは <project>-<version>-<classifier>.jar
の規則となっており、
デフォルトのJARは、my-plugin-1.0.0.jar
、ShadowJarは、my-plugin-1.0.0.all.jar
のように生成される。
このため、 archiveClassifier.set("")
と空を設定することで、classifierが空となりデフォルトのJARとして扱われるようになる。
この設定により ./gradlew publishPlugins
を実行した際に、自動的にリロケーションされた依存関係を含むfat JARがGradle Plugin Portalに公開される。
注意点
サイズがでかくなる
依存ライブラリが含まれる形になるため、サイズが大きくなるためダウンロード時間などが増加する。
JARサイズを小さくしたい場合はminimize()
を使用することで、実際に使用されるクラスのみをJARに含めるようにすることでサイズを小さくできる。
リフレクションで動的に参照されるクラスが除外される可能性はあるので注意が必要。
tasks.shadowJar {
enableRelocation = true
relocatePrefix = "com.mycompany.plugin.internal"
minimize()
}
リフレクションする際のパッケージ名に影響が出る
リローケーションによって名前空間を変更している場合、リフレクションの実装は動作しなくなる可能性がある。 例えば、リフレクションでクラス名を文字列として指定している場合は、リロケーション後のパッケージ名を考慮する必要がある。
// リロケーション後にクラスが見つからない
val clazz = Class.forName("com.google.gson.Gson")
// 定数からクラス名を取得するか、Class.forNameを避ける
val clazz = Gson::class.java
まとめ
Gradleはデフォルトで推移的依存関係により最新が利用されたりと利用する側がチェックして解決しないといけない点が多い。
プラグインなどを提供する際には利用する側の考慮事項を減らす上で有用な選択肢だと考えるので、注意点に記載のデメリットを考慮した上利用していけると良い。