概要
fatJarとは、Javaアプリケーション本体と依存ライブラリのすべてを1つのJarに固めたものです。今回「特定のライブラリは除外したfatJarを作りたい」という事情が発生しました。
fatJarはそれ単体で実行可能であることが大きなメリットなので、特定のものを除外する必要はあまりないかもしれませんが、今回調べたことをまとめておこうと思います。
環境
下記の環境でfatJarの作成を行いました。
Java : 21
gradle : 9.2.1
shadow : 9.3.0
shadowとはgradleでfatJarを作る際に広く利用されているpluginです。
上記のgithubとUserGuideを参考にfatJarの作成を行いました。
利用したコード
私が利用したbuild.gradleの大まかなコードを記載しておきます。以下ではこのコードを変更しながら成果物がどのように変わるのかを見ていきます。
plugins {
id "java"
id 'com.gradleup.shadow' version '9.3.0'
}
repositories {
mavenCentral()
}
apply plugin: "java"
apply plugin: "com.gradleup.shadow"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
dependencies {
// dependency1
}
shadowJar {
archiveBaseName = project.name
dependencies {
// dependency2
}
}
jarタスクに関連する依存関係
上記のサンプルコードにおけるdependency1の部分です。ここに依存関係を追記します。下記の3つdependencyの場合において、どのような挙動になるのかを見てみます。
- implementation
- runtimeOnly
- compileOnly
dependency1の部分を下記のように書き換えてみます。
dependencies {
implementation ("commons-lang:commons-lang:2.6")
runtimeOnly ("org.apache.httpcomponents:httpclient:4.5.13") {
exclude group: 'commons-logging', module: 'commons-logging'
}
compileOnly ("com.google.guava:guava:32.1.2-jre")
}
./gradlew shadowJarコマンドを実行し、fatJarを作ってみます。
implementation
implementationはjarを実行する際に必要なものなので、成果物のfatJarには含まれていそうな気がしますが、実際にどうなったのかでコンパイルして見てみます。
org.apache.commons.langのパッケージが存在するので、implementationで定義したcommons-langは成果物に含まれていました。
runtimeOnly
runtimeOnlyはjarをコンパイルするときは不要ですが、実行時には必要なものなのでfatJarに含まれてほしい気がします。
mozilaとorg.apache.httpのパッケージが存在するので、runtimeOnlyで定義したhttpclientは成果物に含まれていました。
httpclientは推移的な依存関係を持っていて、下記のようになっています。
+--- org.apache.httpcomponents:httpclient:4.5.13
| +--- org.apache.httpcomponents:httpcore:4.4.13
| +--- commons-logging:commons-logging:1.2
| \--- commons-codec:commons-codec:1.11
この中でcommons-logging:commons-loggingは意図的にexcludeしました。よって下記のようになることが期待されます。
- commons-loggingは成果物の中に含まれない
- httpcoreとcommons-codecは成果物に含まれる
org.apache.commons.loggingパッケージが存在せず、org.apache.commons.codec、org.apache.http.annotation等のhttpclientに含まれないパッケージが存在しているので予想通りの結果になっていることがわかります。
compileOnly
compileOnlyはコンパイル時にのみ必要で、実行時には不要なのでfatJarに含まれている必要はないと思われます。
com.googleのパッケージが存在しないため、compileOnlyで定義したguavaは成果物に含まれていませんでした。
ここまでのまとめ
- implementation、runtimeOnlyでdependencyを定義した場合はfatJarに内包される
- compileOnlyでdependencyを定義した場合はfatJarに内包されない
- 推移的な依存ライブラリも考慮され、excludeした場合は除外される
shadowJarタスクでのカスタマイズ
jarタスクのdependencyとは別に、shadowJarのタスクにもいくつかのオプションがあり、成果物をカスタマイズすることができます。
filtering
下記のように、exclude、includeを記述して成果物に含めるファイルをフィルタリングすることができます。
shadowJar {
archiveBaseName = project.name
exclude "qiita/QiitaObject.class"
}
こうすることでexcludeに指定したclassファイルは成果物のfatJarから除外されます。いろいろ調べてみた感じだと下記のようなものをexcludeするために使われることが多いようです。
exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
exclude 'META-INF/*.RSA'
dependency
成果物のfatJarから除外する依存関係を定義することができます。dependency2の部分を下記のように書き換えます。
dependencies {
exclude(dependency("commons-lang:commons-lang"))
}
このようにしてshadowJarを作成すると、commons-langが成果物の中から消えます。
org.apache.commons.langのパッケージがなくなりました。
公式によると、推移的な依存関係を含んだexcludeはサポートされていないようです。
relocate
shaded-jarのように、内包するライブラリのパッケージを変更することができます。
shadowJarのタスクにrelocateの記述を追記します。
shadowJar {
archiveBaseName = project.name
dependencies {
exclude(dependency("commons-lang:commons-lang"))
}
relocate 'org.apache.http.annotation', 'qiita.annotation'
}
httpcoreのorg.apache.http.annotationのパッケージをqiita.annotationパッケージに移動します。成果物のfatJarを確認するとパッケージが移動していることが確認できました。





