推移的依存関係の競合解決-良い、悪い、悪

序文の代わりに



次の土曜日、 EvgenyBorisovJUG.ru でサンクトペテルブルクで公演します。 楽しいスラッシュと興味深い情報がたくさんあり(境界線がどこに行くのかわからないこともあります)、私のスピーチの1つは、WTFスタイルのモジュール式プログラム開発に専念します。 いくつかのホラー映画を紹介します。そのうちの1つは、プロジェクト内の依存関係を誰もが素早く、柔軟かつ正しく説明しようとする方法と、通常は何が起こるかについてです。 面白い? それから地獄へようこそ!





むしろ、「良い、快適で、WTF番目」です。





ちょっとした理論...



依存関係マネージャーとは何ですか?なぜ必要なのですか?


最新のビルドツール(特にJVMの世界)には、別名依存関係マネージャーが含まれています(または、簡単に接続できます)。 もちろん最も有名なのは、 Apache MavenApache Ivy (Apache Antの依存関係マネージャー)、 Gradleです。

JVMの世界におけるビルドツールの主な機能の1つは、クラスパスを作成することです。 これらは、コードをコンパイルし、テストを実行し、アーカイブ(war、ears、または配布物とインストーラー)をビルドし、さらにIDEでプロジェクトをセットアップするために、アセンブリプロセスで多く使用されます。

ネットワークの検索、依存関係のダウンロード、保存、構成のプロセスを容易にするために、依存関係マネージャーがあります。 たとえばcommons-lang



やvoilaなどが必要だと宣言します。



推移的依存関係とは何ですか?


推移的依存関係は、プロジェクトの直接依存関係が依存する成果物です。 次の状況を想像してください。



プロジェクトA



は、 E



B



2つのアーティファクトに依存していますB



これで、直接依存関係が終了し、 推移的C, D



)が開始します。 その結果、アーティファクトを繰り返すことができる依存関係のチェーンを取得します(この例ではD







なぜ推移的な依存関係が必要なのですか?


明らかにコンパイル用ではありません。 しかし、他のすべてのために、おそらく、それらが必要です-これらのアーティファクトは、アーカイブアセンブリ内にある必要があり、テストを実行するためのクラスパスにある必要があり、IDEへの統合のためにAPIを介してそれらのパスを指定する必要があります



対立はどのように形成されますか?


上の図を見ると、まさに矛盾が見られます。 プロジェクトA



のクラスパスには、アーティファクトD



バージョン1(Eに依存)とアーティファクトD



バージョン2( C



依存)の両方が含まれている必要があります!



なぜこれが悪いのですか?


JVM(およびjavac)は、名前(およびクラスローダー、ただしこの単純な例では、すべてのクラスが1つのクラスローダーでロードされる)によってクラスの一意性を決定します。 クラスパスに同じ名前の2つのクラスが見つかった場合、最初のクラスのみがロードされます。 D1



D2



同じ名前のクラスD2



と仮定すると(おそらく同意する必要があります)、生成されたクラスパスに登録されるjarのクラスは、2番目のクラスではロードされません。 どちらが最初ですか? それは依存関係マネージャーのロジックに依存し、一般的には不明です。

ご存じのとおり、これは競合です。





どうする



誰が責任を負うのかは明らかです(Java、あなたが考えたものではありません)が、何ができるでしょうか?

推移的な依存関係の競合を解決するための戦略はいくつかあります(それらの一部は論理的であり、他は不合理です)が、もちろん、特効薬はありません。 それらのいくつかを見てみましょう:



誰が何ですか?



では、前述のどのDer Grosse Troikaができるか見てみましょう。



アパッチアイビー


紛争管理者の面ではアイビーは素晴らしいです。 それらはプラグインであり、両方のオプションが最新であり、同様に失敗し、すべてが箱に入っています。 カスタムでは、すべても美しいです。 ロジックを完全に実装することも、半製品を使用して適切な正規表現を作成することもできます。 デフォルトでは、最新版が実行されています(バージョン管理されています)。



グラドル


Gradleの最初のバージョン(0.6より前、私の記憶が正しければ)Ivyは依存関係マネージャーとして使用されました。 したがって、上記のすべてはGradleにも当てはまりますが、Gradlewareのメンバーは依存関係マネージャーを作成しました(主にGradleの主な利点の1つである並列アセンブリ中のIvyローカルストレージの問題による)。 マネージャーをリリースする過程で、競合マネージャーを置き換えるなどの「マイナーな」機能がロードマップに深く入り込んでおり、かなり長い間、Gradleは最新のもののみで存在していました。 私は最新のものが好きではありません-推移的な依存関係をオフにして、先に進み、すべてを手動でリストします。 しかし、今日はすべて順調です。 1.0からはfailがあり、1.4ではcustomもあります。



Apache Maven


さて、次の写真のために、投稿全体が構想されました。

どのバージョンのDがクラスパスに分類されると思いますか? D1? D2? 両方? じゃない? アセンブリは落ちますか?



おそらく既に推測したように、 D1はクラスパスに分類されます (D1に存在しない新しい機能のために記述されたCのコードはすべて単純に分類されるため、問題が発生する可能性が非常に高くなります)。 これは私があなたに約束した素晴らしいWTFです。 Mavenは、プロジェクトツリー内のプロジェクトのルート(A)に近いクラスパス内のアーティファクトを選択する一意の最も近い戦略を使用します。





どうして? なんてナンセンス?


問題の根本は、Mavenの依存関係を排除することの難しさにあります。 たとえば、D1ではなくD2を使用する場合は、Mavenに次のように伝える必要があります。DearMaven、決してD1は使用しないでください。 ちょうど例として、Gradleでは次のように記述します。

 configurations {all*.exclude group: 'mygroup', module: 'D', version: '1'}
      
      





問題は、これをMavenで表現することが不可能であることです。 (Mavenの経験豊富な、したがって気配りのある思慮深いユーザーは、ここで「エンフォーサープラグインはどうですか?!」と宣言しますが、これは間違っています )。 モジュールEに対して具体的に言うことができます。「Dに依存していると思いましたか? だから、彼女はそこにいません。」 これは良い方法です。競合はもうありません。クラスパスのD2が勝ちます。 しかし、このソリューションは完全にスケーラブルです。 多数のアーティファクトがD1に依存している場合はどうなりますか? 推移性のさまざまなレベルで?



さて、どこが最も近いですか?


グローバル除外が存在しないという問題は、Mavenで非常に「興味深い」方法で解決されました。 プロジェクトAで特定のバージョンの依存関係を宣言した場合、このバージョンのみがクラスパスに分類されることが決定されました。 つまり、実際には、これは究極の最も近いものです-A(これはルート)よりも近いことはできないため、競合は解決されます。Dを除外する必要があるすべての場所を探す必要はありません。 AがDを直接宣言していない場合(この例を参照)、つまりそうです。



興味深いことに、「ユーザーが自分自身を法律と宣言した」という考え方はGradleでも使用されています。これは、最新のような正気の戦略を使用することを妨げません。



更新:コメントの数人は、 forcer-pluginに失敗した機能があることを思い出しましたこれにより、問題が部分的に解決されます。 残り:1.デフォルトで最も近いワイルド。 2.問題の解決策-プロジェクト内のすべての競合する依存関係を(許容範囲内で)登録するか、無限に除外します(地獄)。



そして同じ深さなら?


この美しい質問(例BがD2に依存していた場合の対処方法)は、2年半(2005年10月のリリース2.0から2008年4月のバージョン2.0.9まで)Mavenのメンバーには発生しませんでした。クラスパスにどのようなアーティファクトがあるのか​​はあいまいでした。 Maven 2.0.9で強い意思決定が行われました-最初のものがあります!



これはどのように役立ちますか? そうです、何もありません。 一般的な場合、どちらが最初になるかわからないからです。なぜなら、競合が発生するまで(またはこの謎を調査し始めるまで)推移的な依存関係は現れないからです。 みんなありがとう!



エピローグの代わりに



もちろん、MavenのWTFの洞察は、代替マインドの奇跡的な生成、つまり最も近い戦略に限定されません。 しかし、今日はそれで十分だと思います。 コメントのホリバーは大歓迎です(もしあれば、Gradleに沈むでしょう)。そして、すべてのPetersburgersは、土曜日、ペトロコングレスの31日、宴会を続けるためにJUGに来ます。



All Articles