Java依存関係を管理するためのベストプラクティス
2022年8月26日
0 分で読めますJavaアプリケーションの開発は楽しく、利用できるリソースも豊富です。開発をスピードアップするため、多くの開発者が、作業の一部を担ってくれるフレームワークやライブラリを利用しています。最新のJavaアプリケーションを見ると、ほとんどすべてに、他の誰かが開発したライブラリへの依存関係が含まれています。
バイナリの約80~90%を依存関係が占めています。そのため、Javaプロジェクトを作成する際は依存関係を適切に管理する必要があります。この記事では、プロジェクト内のJava依存関係を扱う際のアドバイスとベストプラクティスをご紹介します。
Javaの依存関係を把握することが重要な理由
コードの変更を管理する際、新しいコードをメインブランチにマージする前の初期的な品質保証として、一般的にコードレビューなどのプロセスを利用します。詳しくは、Javaコードレビュー向けツールのガイドをご覧ください。ペアプログラミングも、こうした品質管理を行う方法の一つです。
しかし、依存関係の扱い方は、自分たちのコードの扱い方と大きく異なります。依存関係は、検証を一切行わずに利用されることが少なくありません。また、トップレベルの依存関係が、何段階にも及ぶ推移的依存関係を取り込むこともよくあります。たとえば、直接依存関係が5つある200行のSpringアプリケーションが、合計60個の依存関係を使うことになり、本番環境に出荷されるコードが約50万行に達する場合があります。
レガシープロジェクトでJavaの依存関係を更新するのは困難な場合があります。依存関係が古いと、互換性の問題が連鎖的に発生し、一つのライブラリを更新するために、バグやセキュリティ上の問題を理由に複数のライブラリを更新しなければならないこともあります。Javaの依存関係のAPIが変更されれば、アプリケーション全体を書き直す必要が生じる可能性もあります。
さらに、大規模なエンタープライズアプリケーションでは、コード内で使われなくなった後も、依存関係がマニフェストファイルに残っていることがよくあります。こうした未使用の依存関係も、プログラム内で引き続き利用可能な状態です。
こうした状況は、次のような問題につながる可能性があります。
バイナリの肥大化によるリソース消費や起動時間の増加
新しい依存関係を追加する際に、ライブラリ同士が競合する可能性
バグやセキュリティ上の問題を含む古いライブラリ
ライブラリ更新時の互換性の問題
その他
Javaの依存関係を管理する
Maven Centralなどのリポジトリを本格的に利用するうえで、ベストプラクティスの一つは、独自のリポジトリマネージャーを構築することです。これは、社内の開発環境とパブリックリポジトリの間に設置する専用のプロキシサーバーです。ビルドの高速化と安定化に役立つだけでなく、Javaパッケージに対するポリシーも設定できます。たとえば、特定のバージョンをブロックし、アプリケーションにダウンロードして使用できないようにできます。
リポジトリマネージャーについての詳細や製品一覧は、Mavenのドキュメントをご覧ください。
Javaプロジェクトに新しい依存関係を追加する
問題を解決するために利用できるライブラリがある場合は、それをJavaの依存関係マニフェストファイルに追加したくなるでしょう。ただし、追加する前に、次の点を検討してください。
問題を解決できるか?
パッケージをインポートする主な理由は、問題を解決することです。選んだ依存関係で問題を解決できるでしょうか。また、新たな課題を生じさせることなく、問題全体を解決できるでしょうか。そうでなければ、より良い方法が見つかるかもしれません。
パッケージ全体が必要か?
必要なのが一つの関数だけなのに、多くの関数やデータ型を含む大規模な依存関係をインポートする価値はあるでしょうか。場合によっては、その関数を自分で実装するほうが簡単で管理しやすいこともあります。たとえば、Tupleデータ型を使いたいだけなら、Eclipse Collections全体を追加する意味があるでしょうか。おそらくありません。
mvnrepository.comをざっと見ると、このパッケージのサイズは約10MBです

また、すでに導入している依存関係で目的を果たせないか確認しましょう。同様の関数やデータ型が、すでに利用できるかもしれません。一方、新たに大規模なライブラリを追加すれば、一度に複数の問題を解決できる場合もあります。状況に応じて判断しましょう。
コントリビューターは何人いるか?
利用するJava依存関係のメンテナーが一人、あるいは数人しかいない場合、バス係数はかなり低くなります。メンテナーがプロジェクトを離れたり、バグを修正する時間がなかったりしたらどうなるでしょうか。あるいは、自分でプロジェクトに貢献することもできます。関係者全員にとって、より安全なプロジェクトにすることにつながります。
ただし、プロジェクトに依存関係を追加する前に、必ず元のリポジトリを確認し、現在活動しているメンテナーが何人いるかを調べましょう。

現在もメンテナンスされているか?
パッケージがメンテナンスされていない場合、それに依存するのは避けるべきです。パッケージを組み込む前に、GitHubリポジトリに最近のプッシュがあるかを確認し、リリースサイクルも調べましょう。パッケージがどの程度適切にメンテナンスされているかを判断できます。

パッケージの最新バージョンは何か?
コード例は、特定のJava依存関係について理解を深めるのに役立ちます。しかし、例が古く、対象のパッケージがすでに更新されている場合もあります。最新の安定版を使うことを検討しましょう。Eclipse Collectionsの場合、mvnpackage.comによると、最新の安定版は2022年7月5日リリースの11.1.0です。このバージョンの利用を検討してください。
この画像には11.1.0.M2も記載されています。これは明らかにプレリリース版です。確実な理由がない限り、本番アプリケーションには安定版のみを含めてください。一般的な目安として、次のような修飾子が付いたバージョンは含めないでください。
alphaまたはa
betaまたはb
milestoneまたはm
rcまたはcr
snapshot
Javaの依存関係にGAまたはfinalの修飾子が付いている場合、一般的に安定版と考えてよいでしょう。

セキュリティ上の脆弱性はあるか?
Javaパッケージに依存する前に、既知の脆弱性がないかスキャンしてください。Snyk CLIは、MavenやGradleのファイルをスキャンするのに最適なツールです。利用するライブラリにセキュリティ上の脆弱性が含まれている場合は、別のパッケージを選ぶことを検討してください。
Javaの依存関係を更新する
新しいバージョンがあるか?
Javaの依存関係を一つずつ手作業で確認し、新しいバージョンがあるか調べるのは避けたいものです。幸い、もっと簡単な方法があります。パッケージマネージャーのプラグインを使えば、たとえばビルドのたびに、希望する頻度で依存関係を自動的に確認できます。
ツールによっては、ベータ版やプレリリース版が提示されることがあります。ライブラリは安定版のみを使用することを強くおすすめします。
Mavenの例
Mavenでは、以下のようにversionsプラグインを使用できます。pom.xmlに特別な設定を追加する必要はありません。

Gradleの例
Gradleでは、ben-manesのversionsプラグインなどを追加する必要があります。
これで、同様のコマンドを実行し、ライブラリの新しいバージョンを表示できます。

IntelliJ IDEA
IntelliJ IDEAを使用している場合は、更新可能な依存関係に新しいバージョンを示す下線が表示されます。MavenプロジェクトとGradleプロジェクトの両方で利用できます。

Snyk
GitHubリポジトリをSnykアカウントに接続すると、プルリクエストごとに推奨される修正や更新を提示できます。包括的なセキュリティに関するアドバイスとあわせて、Javaの依存関係を最新の状態に保つのに役立ちます。

利用しているパッケージは、現在もメンテナンスされているか?
GitHubのリポジトリやmvnpackage.comを再度確認し、最近の更新やコミットがあるか調べるとよいでしょう。パッケージが十分にメンテナンスされていないようなら、自分でメンテナンスするか、より頻繁に更新されている別のライブラリに移行できます。
ただし、アプリケーションに不可欠な依存関係で問題が見つかった場合は、自分で問題を修正し、その修正をオープンソースプロジェクトに貢献することを検討してください。大変喜ばれるでしょうし、問題を報告してメンテナーに修正を促すより、早く解決できることもよくあります。
Javaの依存関係にセキュリティ上の問題はあるか?
現在、アプリケーションに脆弱性がなくても、今後もずっと安全とは限りません。新たな脆弱性やエクスプロイトは、日々発見され、公開されています。そのため、ライブラリに脆弱性がない状態を保つには、定期的に再スキャンする必要があります。
Snykでは、依存関係のスキャンを開発ライフサイクルに組み込む方法を複数ご用意しています。ローカルマシンではSnyk CLIのほか、IntelliJ、Eclipse、VS Codeとのインテグレーションを使って脆弱性をスキャンできます。ビルドサイクル中にMavenやGradle(非公式)プラグインでスキャンしたり、CIパイプラインとのインテグレーションを利用したりすることもできます。また、GitリポジトリをSnykに追加すれば、プロジェクトを毎日スキャンして更新できます。

プロジェクトからJavaの依存関係を削除する
そのパッケージはまだ使われているか?
Javaの依存関係を使わなくなった場合は、マニフェストファイルから削除しましょう。ファイルに記載されたすべてのパッケージはバイナリの一部となり、クラスパス上で利用可能になります。未使用の依存関係を削除すると、バイナリが小さくなり、起動時間やダウンロード時間が短縮されるだけでなく、セキュリティも向上します。クラスパス上の依存関係を最小限に抑えることは、デシリアライゼーション・ガジェットチェーンなどの攻撃から保護するうえで重要です。
デスクをきれいに保つポリシー、つまりここではアプリをすっきり保つポリシーを徹底することを強くおすすめします。幸い、パッケージマネージャーを使えば、未使用のJava依存関係を特定できます。
Mavenの例
Mavenではdependencyプラグインを使って依存関係を分析できます。このプラグインは、宣言したJava依存関係がコード内でも使用されているかを確認します。
この場合、providedやtestの依存関係は対象にしたくないため、ignoreNonCompileフラグを使います。

Gradleの例
Gradleでは、依存関係の分析に別のプラグインを追加する必要があります。ここではnebula.lintプラグインを使用します。このGradleリンターは、追加したJava依存関係を分析し、未使用の依存関係がないかを確認できます。
プラグインを設定して、gradleLint.rulesを指定する必要があります。Gradleファイル内で設定することも、コマンドラインパラメーターとして指定することもできます。以下の例では、後者を選んでいます。アプリケーション向けのプラグイン設定について詳しくは、プラグインのドキュメントをご覧ください。

Javaアプリケーション向けに、確かな依存関係管理戦略を策定する
Javaアプリケーションの開発でライブラリやフレームワークなどの依存関係を利用する場合、それらをどのように扱うかを定めた戦略を作ることが重要です。アプリケーション内のJava依存関係を選択、更新、削除する方法を理解することは、セキュリティに欠かせません。明確な戦略を策定すれば、優先度の高いセキュリティ問題への対応でパッケージの更新が必要になったときも、慌てずに済みます。
オープンソースの依存関係管理について詳しくは、こちらの記事をご覧ください。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。



