6
1

gradleで作るfatJarをカスタマイズしたい

Posted at

概要

fatJarとは、Javaアプリケーション本体と依存ライブラリのすべてを1つのJarに固めたものです。今回「特定のライブラリは除外したfatJarを作りたい」という事情が発生しました。

fatJarはそれ単体で実行可能であることが大きなメリットなので、特定のものを除外する必要はあまりないかもしれませんが、今回調べたことをまとめておこうと思います。

環境

下記の環境でfatJarの作成を行いました。

Java : 1.8
gradle : 7.6.1
shadow : 7.1.2

shadowとはgradleでfatJarを作る際に広く利用されているpluginです。

上記のgithubとUserGuideを参考にfatJarの作成を行いました。

利用したコード

私が利用したbuild.gradleの大まかなコードを記載しておきます。以下ではこのコードを変更しながら成果物がどのように変わるのかを見ていきます。

plugins {
  id "java"
  id 'com.github.johnrengelman.shadow' version '7.1.2'
}

repositories {
  mavenCentral()
}

apply plugin: "java"
apply plugin: "com.github.johnrengelman.shadow"

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'

dependencies {
  // dependency1
}

shadowJar {
  baseName = 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には含まれていそうな気がしますが、実際にどうなったのかでコンパイルして見てみます。

image.png

org.apache.commons.langのパッケージが存在するので、implementationで定義したcommons-langは成果物に含まれていました。

runtimeOnly

runtimeOnlyはjarをコンパイルするときは不要ですが、実行時には必要なものなのでfatJarに含まれてほしい気がします。

image.png

mozilaorg.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は成果物に含まれる

image.png

org.apache.commons.loggingパッケージが存在せず、org.apache.commons.codecorg.apache.http.annotation等のhttpclientに含まれないパッケージが存在しているので予想通りの結果になっていることがわかります。

compileOnly

compileOnlyはコンパイル時にのみ必要で、実行時には不要なのでfatJarに含まれている必要はないと思われます。

image.png

com.googleのパッケージが存在しないため、compileOnlyで定義したguavaは成果物に含まれていませんでした。

ここまでのまとめ

  • implementation、runtimeOnlyでdependencyを定義した場合はfatJarに内包される
  • compileOnlyでdependencyを定義した場合はfatJarに内包されない
  • 推移的な依存ライブラリも考慮され、excludeした場合は除外される

shadowJarタスクでのカスタマイズ

jarタスクのdependencyとは別に、shadowJarのタスクにもいくつかのオプションがあり、成果物をカスタマイズすることができます。

filtering

下記のように、exclude、includeを記述して成果物に含めるファイルをフィルタリングすることができます。

shadowJar {
  baseName = 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が成果物の中から消えます。

image.png

org.apache.commons.langのパッケージがなくなりました。

公式によると、推移的な依存関係を含んだexcludeはサポートされていないようです。

relocate

shaded-jarのように、内包するライブラリのパッケージを変更することができます。

shadowJarのタスクにrelocateの記述を追記します。

shadowJar {
  baseName = project.name
  dependencies {
    exclude(dependency("commons-lang:commons-lang"))
  }
  relocate 'org.apache.http.annotation', 'qiita.annotation'
}

httpcoreorg.apache.http.annotationのパッケージをqiita.annotationパッケージに移動します。成果物のfatJarを確認するとパッケージが移動していることが確認できました。

image.png

参考文献

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1