JavaでLinkageErrorsを処理する方法は?
-
05-07-2019 - |
質問
非常にXMLベースのJavaアプリケーションの開発中、Ubuntu Linuxで興味深い問題に最近遭遇しました。
Java Plugin Framework を使用するアプリケーションは、 dom4j で作成したXMLドキュメントを Batik'sに SVG仕様の実装。
コンソールで、エラーが発生したことがわかりました:
Exception in thread "AWT-EventQueue-0" java.lang.LinkageError: loader constraint violation in interface itable initialization: when resolving method "org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;" the class loader (instance of org/java/plugin/standard/StandardPluginClassLoader) of the current class, org/apache/batik/dom/svg/SVGOMDocument, and the class loader (instance of <bootloader>) for interface org/w3c/dom/Document have different Class objects for the type org/w3c/dom/Attr used in the signature at org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149) at org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361) at org.dom4j.io.DOMWriter.write(DOMWriter.java:138)
この問題は、JVMの元のクラスローダーとプラグインフレームワークによってデプロイされたクラスローダーの競合が原因であると考えています。
私の知る限り、フレームワークが使用するクラスローダーを指定することはできません。ハッキングすることは可能かもしれませんが、この問題を解決するための攻撃的なアプローチは、Linuxシステムでのみ発生するため(何らかの理由で)好まれます。
このような問題に遭遇した人がいて、それを修正する方法、または少なくとも問題の核心にたどり着く方法を知っていますか?
解決
LinkageErrorは、複数のクラスローダーによってロードされたクラスCがあり、それらのクラスが同じコード(比較、キャストなど)で一緒に使用されている古典的な場合に発生するものです。同じクラス名であっても、同じjarからロードされても、別のクラスローダーからロードされた場合、あるクラスローダーからのクラスは常に別のクラスとして扱われます。
(長年にわたって大幅に改善された)メッセージには次のように書かれています。
Exception in thread "AWT-EventQueue-0" java.lang.LinkageError:
loader constraint violation in interface itable initialization:
when resolving method "org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;"
the class loader (instance of org/java/plugin/standard/StandardPluginClassLoader)
of the current class, org/apache/batik/dom/svg/SVGOMDocument,
and the class loader (instance of ) for interface org/w3c/dom/Document
have different Class objects for the type org/w3c/dom/Attr used in the signature
したがって、ここでの問題は、org.w3c.dom.Attr(標準DOMライブラリの一部)を使用するSVGOMDocument.createAttribute()メソッドを解決することです。しかし、BatikでロードされたAttrのバージョンは、メソッドに渡すAttrのインスタンスとは異なるクラスローダーからロードされました。
BatikのバージョンはJavaプラグインからロードされているようです。そして、あなたのものは&quot;からロードされています&quot;は、組み込みJVMローダー(ブートクラスパス、ESOM、またはクラスパス)の1つである可能性が高いです。
3つの顕著なクラスローダーモデルは次のとおりです。
- 委任(JDKのデフォルト-親に尋ね、次に私)
- 委任後(プラグイン、サーブレット、隔離したい場所で一般的-私に聞いてから親)
- 兄弟(OSGi、Eclipseなどの依存関係モデルに共通)
JPFクラスローダーが使用する委任戦略はわかりませんが、重要なのは、1つのバージョンのdomライブラリをロードし、全員が同じ場所からそのクラスを調達することです。それは、クラスパスからそれを削除してプラグインとしてロードするか、Batikがそれをロードするのを防ぐこと、または他の何かを意味するかもしれません。
他のヒント
クラスローダーの階層の問題のように聞こえます。アプリケーションがどのタイプの環境にデプロイされているかわかりませんが、この問題はWeb環境で発生することがあります。アプリケーションサーバーがクラスローダーの階層を作成し、次のようになります。
javahome / lib-ルートとして
appserver / lib-ルートの子として
webapp / WEB-INF / lib-ルートの子の子として
など
通常、クラスローダーは親クラスローダー(&quot; parent-first
&quot;として知られています)にロードを委任し、そのクラスローダーがクラスを見つけられない場合、子クラスローダーはそれを試みます。たとえば、webapp / WEB-INF / libにJARとしてデプロイされたクラスがクラスをロードしようとすると、まずappserver / libに対応するクラスローダーにクラスをロードするように要求します(これはjavahome / libに対応するクラスローダーに要求します)クラスをロードします)、このルックアップが失敗した場合、WEB-INF / libでこのクラスに一致するものが検索されます。
Web環境では、この階層で問題が発生する可能性があります。たとえば、私が以前に遭遇した間違い/問題の1つは、WEB-INF / libのクラスがappserver / libにデプロイされたクラスに依存し、それがWEB-INF / libにデプロイされたクラスに依存する場合です。これにより、クラスローダーが親クラスローダーに委任できる一方で、ツリーの下に委任できないため、エラーが発生しました。したがって、WEB-INF / libクラスローダーはappserver / libクラスローダーにクラスを要求し、appserver / libクラスローダーはそのクラスをロードして依存クラスをロードしようとしますが、appserver / libまたはjavahomeでそのクラスが見つからなかったため失敗します/ lib。
そのため、アプリをWeb /アプリサーバー環境にデプロイしていないかもしれませんが、クラスローダーの階層が設定されている場合、長すぎる説明が当てはまるかもしれません。しますか? JPFは、プラグイン機能を実装できるように、何らかのクラスローダーマジックを実行していますか?
これは誰かに役立つかもしれません。この問題は、独自の依存関係を統合することで解決できます。この簡単な手順に従ってください
まず、次のようなエラーを確認します:
- メソッドの実行に失敗しました:
- java.lang.LinkageError:ローダー制約違反:
- メソッドを解決する場合&quot; org.slf4j.impl。 StaticLoggerBinder .getLoggerFactory()Lorg / slf4j / ILoggerFactory;&quot;
- 現在のクラスorg / slf4j / LoggerFactory のクラスローダー(org / openmrs / module / ModuleClassLoaderのインスタンス)、
- および解決されたクラスorg / slf4j / impl / StaticLoggerBinder のクラスローダー(org / apache / catalina / loader / WebappClassLoaderのインスタンス)、
- taticLoggerBinder.getLoggerFactory()Lorg / slf4j / ILoggerFactory型の異なるClassオブジェクトがあります。署名で使用
-
2つの強調表示されたクラスを参照してください。 「StaticLoggerBinder.class jar download」のようにGoogleで検索します。 &amp; &quot; LoggeraFactory.class jar download&quot;。これにより、最初または場合によっては2番目のリンクが表示されます(サイトは http://www.java2s.com です)これは、プロジェクトに含めたjarバージョンの1つです。自分でスマートに識別できますが、Googleにはまっています;)
-
その後、jarファイル名がわかります。私の場合、slf4j-log4j12-1.5.6.jar&amp; slf4j-api-1.5.8
- このファイルの最新バージョンは、 http://mvnrepository.com/ (実際にはすべてのバージョン日付まで、これはmavenが依存関係を取得するサイトです)。
- ここで、両方のファイルを最新バージョンの依存関係として追加します(または両方のファイルバージョンを同じにします。選択したバージョンが古い場合)。以下は、pom.xmlに含める必要がある依存関係です
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
クラスローダーを指定できますか?そうでない場合は、次のようにコンテキストクラスローダーを指定してみてください。
Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(yourClassLoader);
callDom4j();
} finally {
thread.setContextClassLoader(contextClassLoader);
}
私はJavaプラグインフレームワークに精通していませんが、Eclipseのコードを作成し、時々同様の問題に遭遇します。修正する保証はありませんが、おそらく一見の価値があります。
AlexとMattの回答は非常に役立ちます。私も彼らの分析から恩恵を受けることができました。
Netbeans RCPフレームワークでBatikライブラリを使用するときに同じ問題が発生しました。Batikライブラリは「Library Wrapper Module」として含まれています。他のモジュールがXML APIを使用し、そのモジュールに対してBatikに依存する必要がなく、確立されていない場合、同様のエラーメッセージでクラスローダー制約違反の問題が発生します。
Netbeansでは、個々のモジュールは専用のクラスローダーを使用し、モジュール間の依存関係は適切なクラスローダー委任ルーティングを意味します。
Batikライブラリバンドルからxml-apis jarファイルを省略するだけで問題を解決できました。
この質問で指定 a>、 -verbose:class
を有効にすると、ロードされているすべてのクラスに関するJVMログ情報が作成されます。これは、クラスがどこから来ているかをより複雑なシナリオで理解するのに非常に役立ちます&amp;アプリケーション。
得られる出力は、おおよそ次のようになります(その質問からコピー):
[Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]
[Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]