Eclipse / OSGI、Java 11、JAXB、およびクラスローダー
AとBの2つのJavaモジュールがあります。AはJAXBアノテーションを備えたコアモデルと、JAXB関連の作成(コンテキストの作成、マーシャリング、アンマーシャリングなど)のためのヘルパークラスを提供します。Bは@を介してモデルに含まれる追加のクラスを提供します。 XmlAnyElement(lax = true)であるため、JAXBコンテキストに追加する必要があります。
これはプレーンJavaで正常に機能します-Bのクラスローダーは関連するすべてのクラスを確認し、次のコマンドでJAXBコンテキストをインスタンス化できます。
JAXBContext.newInstance(RootFromA.class, RootFromB.class)
今、私はOSGIで同じことを試みています(BはEclipseプラグインであり、AはプレーンJavaコマンドラインモジュールCでも使用されるコアライブラリです)。多くの試行錯誤の末、私はAとBに、JAXBAPIとOSGIパッケージのインポートによる実装の両方を確認させることができました。問題は、上記のようにnewInstanceを呼び出すと、RootFromAのクラスローダーではなく、JAXB APIのクラスローダーが使用されているように見えることです。また、RootFromBのクラスローダーでもありません。そのため、JAXB実装を確認することすらできず、ContextFactoryクラスが見つからないと文句を言います。
別のバージョンのnewInstanceを呼び出すことで、これを解決できました。
JAXBContext.newInstance(
RootFromA.class.getPackageName()
+ ":" + RootFromB.class.getPackageName(),
RootFromB.class.getClassLoader())
私はこれが好きではありません。2つの理由があります。
- 私の「クライアント」コード(BはAのJAXBヘルパーのもののクライアントです)は、適切なクラスローダーを手動で提供する必要があります。
- 私のコードはそれらを完全に認識していて、実際にはクラスからパッケージ名を取得していますが、コンテキストクラスをリストするすべての参照パッケージでjaxb.indexファイルを提供する必要があります。
Bだけがクラスの完全なセットを知っていて、誰のクラスローダーがそれらすべてを見ることができるかを決定できるので、おそらく(1)を回避する方法はありません。Eclipse拡張ポイントを介してBにフックする拡張モジュールCとDを追加し、JAXBコンテキストに追加のクラスを提供すると、さらに問題が発生する可能性があるのではないかと心配しています-Bのクラスローダーはそれらを認識できますか?
しかし、(2)に必要な静的インデックスファイルを取り除く方法を本当に見つけたいと思います。コンテキストクラスの完全なセットは動的であり、プレーンJavaではServiceLoaderによって、Eclipseでは拡張ポイントによって決定されます。どちらの場合も、コンテキストに属する必要のあるクラスの完全なセットに直接アクセスできるため、冗長な各パッケージにjaxb.indexファイルを手動で追加する必要があるため、潜在的で不要なエラーの原因となる可能性があります。
私は何かが足りないのですか?これを行うための「より良い」方法はありますか?そして、なぜコンテキストクラスのセットとクラスローダーを受け取るnewInstanceメソッドがないのですか?
回答
解決策を見つけたようです。私の問題は、2つの異なる目的でクラスローダーを処理する必要があることでした。
- コンテキストをインスタンス化するためにJAXBによって使用されるクラスローダー
- 私のさまざまなモデルクラスのクラスローダー
上記のように、(1)はJAXBContext.newInstance()のパラメーターとして指定できますが、個々のモデルクラスではなくパッケージ名としてモデルを指定する場合に限ります。つまり、JAXBは独自にクラスを検索する必要があり、それを実行するために使用できる唯一のクラスローダーは(1)です。これは、複数のバンドルに分散している場合、すべてのモデルクラスを表示できません。
別のスレッド(Apache Felix内で実行しているときにJAXBがjaxb.indexを見つけられないのはなぜですか?)は、JAXBがデフォルトでスレッドコンテキストクラスローダーを使用してコンテキスト実装を見つけることを教えてくれました。実装を確認するクラスローダー(たとえば、自分のバンドルの実装)に設定するだけで、JAXBに独自の実装を見つけることができます。これにより、クラスの配列を使用して、好みのバージョンのnewInstance()を自由に呼び出すことができます。また、これらのクラスはすでにロードされているため、JAXBはそれらをそのまま使用でき、異なるクラスローダーを気にする必要はありません。
要するに:
Class[] myClasses = getModelClasses(); // gather all model classes, which may have different classloaders
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(getClass().getClassLoader()); // my own bundle's classloader
JAXBContext context = JAXBContext.newInstance(myClasses);
thread.setContextClassLoader(originalClassLoader); // reset context classloader
コンテキストクラスローダーの操作はAutoCloseableでラップできるため、JAXBContextのインスタンス化をtry-with-resourcesブロックでラップするだけで済みます。