はじめに (対象読者・この記事でわかること)
この記事は、JavaアプリケーションをMavenでビルドし、配布可能なJARファイルを作成している開発者の方を対象としています。特に、作成されるJARファイルのサイズが大きすぎると感じている方、あるいは実行環境にすでに存在するライブラリが重複してJARに含まれてしまうことに課題を感じている方に役立つ情報を提供します。
この記事を読むことで、MavenのprovidedスコープとMaven Shade Pluginという強力なツールを効果的に利用し、不要なライブラリをJARファイルから除外する方法が具体的にわかります。これにより、デプロイ可能なJARファイルのサイズを最適化し、依存関係の衝突を避けるための実践的なスキルを習得できます。プロジェクトの配布効率と実行時の健全性を向上させるための第一歩を踏み出しましょう。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
* Java開発の基本的な知識
* Mavenの基本的な使い方(pom.xmlの構造、ビルドコマンドなど)
* 依存関係管理の基本的な概念
Maven Jarに特定のライブラリを含めたくない背景と必要性
JavaプロジェクトをMavenでビルドする際、通常はプロジェクトが依存する全てのライブラリが最終的なJARファイル(特に実行可能な"fat JAR"や"uber JAR"と呼ばれるもの)に含まれます。これは多くの場合便利ですが、いくつかの課題を引き起こすことがあります。
1. JARファイルの肥大化とデプロイの非効率性 不必要なライブラリが多数含まれると、JARファイルのサイズが著しく大きくなります。これは、アプリケーションの配布やデプロイに時間がかかり、ストレージの消費も増大させる原因となります。特に、マイクロサービス環境やコンテナベースのデプロイメントでは、イメージサイズの削減がパフォーマンスや効率に直結します。
2. 依存関係の衝突(Dependency Hell) 複数のアプリケーションやモジュールが同じライブラリの異なるバージョンに依存している場合、実行時に依存関係の衝突が発生する可能性があります。例えば、サーブレットコンテナが特定のバージョンのAPIを提供しているにもかかわらず、アプリケーションのJARに別のバージョンのAPIが含まれていると、予期せぬ挙動やエラーが発生することがあります。
3. リソースの無駄遣い 実行環境(例:アプリケーションサーバー、JDK自体)がすでに提供しているライブラリをJARに含めることは、単なるリソースの無駄遣いです。これにより、メモリフットプリントが増加したり、クラスローダーの複雑さが増したりする可能性があります。
これらの課題を解決するために、Mavenでは特定のライブラリを最終的なJARファイルから除外するメカニズムが提供されています。主な方法として、依存関係スコープのprovidedと、ビルドプラグインであるMaven Shade Pluginの活用があります。これらを適切に使い分けることで、効率的でクリーンなアプリケーションのデプロイを実現できます。
Maven Shade Pluginとprovidedスコープで実現するライブラリ除外テクニック
Mavenはデフォルトで、プロジェクトの依存関係として定義されたライブラリを全てクラスパスに追加し、packageゴールで実行可能なJARを作成する際には、これらの依存関係を結合して単一のJAR(通称「uber JAR」または「fat JAR」)に含めることがあります。しかし、前述の通り、これは常に望ましい挙動ではありません。ここでは、特定のライブラリを除外するための二つの主要なテクニック、「providedスコープの活用」と「Maven Shade Pluginの利用」について詳しく解説します。
providedスコープの活用:実行環境が提供するライブラリの除外
providedスコープは、開発時やコンパイル時には必要だが、実行時には実行環境(例:サーブレットコンテナ、JVM自体)がそのライブラリを提供するため、最終的なJARファイルには含める必要がない依存関係を指定する際に使用します。
具体的な設定方法:
pom.xmlの<dependencies>セクションで、除外したいライブラリの<scope>をprovidedに設定します。
Xml<dependencies> <!-- 例: Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- 例: Lombok (コンパイル時のみ必要) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency> <!-- その他の通常の依存関係 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> </dependencies>
providedスコープの挙動:
* コンパイル時: クラスパスに含まれ、コンパイルが可能です。
* テスト時: クラスパスに含まれ、テストが実行可能です。
* パッケージング時: 最終的なJARファイルには含まれません。
* 実行時: 実行環境にそのライブラリが存在することを前提とします。
使用する状況: * Webアプリケーション開発におけるServlet API, JSP API。 * Enterprise JavaBeans (EJB) アプリケーションにおけるJava EE API。 * Lombokのように、コンパイル時にのみコード生成を行うライブラリ。
Maven Shade Pluginの活用:より柔軟なライブラリの除外・結合
providedスコープは非常に便利ですが、以下のようなケースでは不十分です。
* あるライブラリが、直接providedスコープに指定されていなくても、別の依存関係の推移的依存として含まれてしまう場合。
* 特定のライブラリの一部(特定のクラスファイルやパッケージ)だけを除外したい場合。
* 複数のJARを結合し、特定のライブラリを完全に除外しつつ、それ以外の依存関係を一つのJARにまとめたい場合。
このような場合に、Maven Shade Pluginが強力なツールとなります。このプラグインは、最終的な「uber JAR」を構築する際に、依存関係をフィルタリングしたり、クラスファイルをリネームしたりする機能を提供します。
具体的な設定方法:
pom.xmlの<build>セクション内の<plugins>にmaven-shade-pluginを追加します。
Xml<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.2</version> <!-- 最新バージョンを使用してください --> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <artifactSet> <excludes> <!-- 特定のライブラリ全体を除外する例 --> <exclude>com.google.guava:guava</exclude> <exclude>org.apache.logging.log4j:log4j-core</exclude> <!-- グループIDとアーティファクトIDを指定 --> </excludes> </artifactSet> <filters> <!-- 特定のライブラリ内の特定のファイル(クラス)を除外する例 --> <filter> <artifact>*:*</artifact> <!-- 全てのアーティファクトが対象 --> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> <filter> <artifact>org.bouncycastle:bcprov-jdk15on</artifact> <excludes> <!-- このライブラリから特定のパッケージを除外 --> <exclude>org/bouncycastle/LICENSE.txt</exclude> <exclude>org/bouncycastle/jce/**</exclude> </excludes> </filter> </filters> <createDependencyReducedPom>false</createDependencyReducedPom> <!-- POMの再生成を無効化する場合 --> </configuration> </execution> </executions> </plugin> </plugins> </build>
Shade Pluginの主要な設定要素:
<artifactSet>:<includes>: 最終JARに含めるアーティファクトを指定します。指定がない場合は全ての依存関係が含まれます。<excludes>: 最終JARから除外するアーティファクトを指定します。groupId:artifactIdまたはgroupId:artifactId:version形式で指定します。*を用いたワイルドカードも利用可能です。
<filters>:<filter>要素を使って、特定のアーティファクト内のファイル(クラスファイルやリソースファイル)をより細かく制御できます。<artifact>: フィルタリングの対象となるアーティファクトをgroupId:artifactId形式で指定します。*:*で全てのアーティファクトを対象にできます。<includes>/<excludes>: 指定したアーティファクト内で、含める/除外するファイルパス(ワイルドカード利用可能)を定義します。これはJAR内部のパスと一致します。
<relocations>:- 依存関係の衝突を避けるために、特定のパッケージ名を変更(リロケート)する機能です。
com.google.guavaをshaded.com.google.guavaのように変更し、衝突を避けることができます。
- 依存関係の衝突を避けるために、特定のパッケージ名を変更(リロケート)する機能です。
<createDependencyReducedPom>:trueに設定すると、最終的なJARに含まれる依存関係のみを記述したpom.xmlが生成されます。これはライブラリとして配布する場合に便利ですが、uber JARをアプリケーションとして配布する場合はfalseに設定することが多いです。
ハマった点やエラー解決
1. providedスコープにしたのに実行時NoClassDefFoundErrorが発生する
* 原因: providedスコープに指定されたライブラリは最終JARに含まれないため、アプリケーションの実行環境にそのライブラリが存在しない場合に発生します。
* 解決策: アプリケーションを実行する環境(例: Tomcat, WildFly, Dockerコンテナ)に、必要なライブラリが適切にデプロイされているか、またはCLASSPATHに追加されているかを確認してください。もし実行環境が提供しないライブラリであれば、providedスコープではなく通常のcompileスコープに戻す必要があります。
2. Shade Pluginで除外したはずのライブラリがなぜか最終JARに残ってしまう
* 原因:
* 除外設定の記述ミス(groupIdやartifactIdのタイプミス)。
* 除外したいライブラリが、別のライブラリの推移的依存関係として含まれており、その推移的依存関係がexcludes設定でカバーされていない。
* <filters>と<artifactSet><excludes>の挙動を混同している。
* 解決策:
* mvn dependency:treeコマンドを実行し、プロジェクトの依存関係ツリーを確認してください。除外したいライブラリがどのように含まれているかを把握できます。
* pom.xmlのmaven-shade-plugin設定のexcludesやfiltersセクションを再確認し、正確なgroupId:artifactIdを指定しているか確認してください。推移的依存関係の場合、親となる依存関係も考慮する必要があります。
* Shade Pluginのログを詳細モードで確認し、どの依存関係が処理されているかを追跡します。
3. 特定のクラスのみ除外したいが、Shade Pluginの<filters>でうまくできない
* 原因: <filters>の<excludes>パターンが正確でない、または特定のクラスが複数のJARファイルに存在し、フィルタリングが複雑になっている。
* 解決策:
* JARファイルの中身を(例えばunzip -l target/your-app-shaded.jarなどで)確認し、除外したいクラスファイルの正確なパスを特定します。
* <filter>要素の<artifact>で対象となるライブラリを明確に指定し、<excludes>で正確なパスパターン(例: com/example/mypackage/MyClass.class)を指定します。ワイルドカード(**/*.classなど)も有効活用してください。
* 複数のアーティファクトにわたって特定のクラスを除外したい場合は、それぞれの<filter>エントリで指定するか、*:*と組み合わせることを検討してください。
解決策
これらの問題に対処するためには、以下のベストプラクティスを心がけることが重要です。
- 依存関係の可視化:
mvn dependency:treeコマンドを頻繁に利用し、プロジェクトの依存関係を常に把握する。 - 設定のテスト:
pom.xmlの変更後は必ずmvn clean packageを実行し、生成されたJARファイルの中身(jar tf your-app.jarなど)を確認して、意図した通りにライブラリが除外されているかを検証する。 - 公式ドキュメントの参照: Maven Shade Pluginの公式ドキュメントは非常に詳細です。複雑なケースに遭遇した場合は、公式ドキュメントを参照して最新かつ正確な情報を得るようにしましょう。
これらのテクニックを組み合わせることで、MavenプロジェクトにおけるJARファイルの最適化と、依存関係に起因する問題の解決に大いに役立ちます。
まとめ
本記事では、MavenでビルドされるJARファイルから特定のライブラリを除外するための効果的な方法として、providedスコープの活用とMaven Shade Pluginの利用について解説しました。
providedスコープは、実行環境が提供するライブラリ(例: Servlet API)や、コンパイル時のみ必要なライブラリ(例: Lombok)を最終的なJARから除外する際に非常に有効です。これにより、JARのサイズを小さく保ち、環境との依存関係の重複を避けることができます。Maven Shade Pluginは、より複雑な除外要件に対応するための強力なツールです。特定のライブラリ全体だけでなく、その一部のクラスファイルを除外したり、推移的依存として含まれる不要なライブラリを排除したりすることが可能です。また、依存関係の衝突を避けるためのパッケージのリロケーション機能も提供します。
この記事を通して、Mavenのビルドプロセスをより深く理解し、アプリケーションの配布サイズを最適化することで、デプロイ効率の向上、依存関係の衝突回避、そしてリソースの有効活用といったメリットが得られたことと思います。
今後は、マルチモジュールプロジェクトにおけるこれらの設定の適用方法や、Shade Pluginのより高度なリロケーションやリソース変換の活用方法についても掘り下げていく予定です。
参考資料
