37

複数のClassLoaderがある環境でServiceLoaderを使用するためのベストプラクティスは何ですか?ドキュメントでは、初期化時に単一のサービスインスタンスを作成して保存することを推奨しています。

private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);

これにより、現在のコンテキストクラスローダーを使用してServiceLoaderが初期化されます。ここで、このスニペットがWebコンテナの共有クラスローダーを使用してロードされたクラスに含まれており、複数のWebアプリケーションが独自のサービス実装を定義したいとします。これらは上記のコードでは検出されません。ローダーが最初のwebappsコンテキストクラスローダーを使用して初期化され、他のユーザーに間違った実装を提供する可能性さえあります。

毎回サービスファイルを列挙して解析する必要があるため、常に新しいServiceLoaderを作成することはパフォーマンスの面で無駄に思えます。編集:これは、JavaのXPath実装に関するこの回答に示されているように、パフォーマンスの大きな問題になる可能性もあります。

他のライブラリはこれをどのように処理しますか?クラスローダーごとに実装をキャッシュしますか、毎回構成を再解析しますか、それとも単にこの問題を無視して1つのクラスローダーでのみ機能しますか?

4

5 に答える 5

67

私は個人的には好きではありませんServiceLoader。遅くて不必要に無駄が多く、最適化するためにできることはほとんどありません。

また、少し制限があることもわかりました。タイプだけで検索する以上のことをしたい場合は、本当に邪魔にならないようにする必要があります。

xbean-finder の ResourceFinder

  • ResourceFinderは、ServiceLoader の使用を置き換えることができる自己完結型の Java ファイルです。コピー&ペーストの再利用は問題ありません。これは 1 つの Java ファイルであり、ASL 2.0 のライセンスを受けており、Apache から入手できます。

注目のスパンが短くなりすぎる前に、ServiceLoader を置き換える方法を次に示します。

ResourceFinder finder = new ResourceFinder("META-INF/services/");
List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);

META-INF/services/org.acme.Pluginこれにより、クラスパス内のすべての実装が見つかります。

実際にすべてのインスタンスをインスタンス化するわけではないことに注意してください。必要なものを選択すると、1 回のnewInstance()呼び出しでインスタンスが作成されます。

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

  • newInstance()適切な例外処理で呼び出すのはどれくらい難しいですか? 難しくない。
  • 必要なものだけを自由にインスタンス化できるのは素晴らしいことです。
  • コンストラクタ引数をサポートできるようになりました!

検索範囲の絞り込み

特定の URL だけを確認したい場合は、次のように簡単に行うことができます。

URL url = new File("some.jar").toURI().toURL();
ResourceFinder finder = new ResourceFinder("META-INF/services/", url);

ここでは、この ResourceFinder インスタンスの使用時に「some.jar」のみが検索されます。

またUrlSet、クラスパスから URL を簡単に選択できる便利なクラスもあります。

ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); 
UrlSet urlSet = new UrlSet(webAppClassLoader);
urlSet = urlSet.exclude(webAppClassLoader.getParent());
urlSet = urlSet.matching(".*acme-.*.jar");

List<URL> urls = urlSet.getUrls();

代替の「サービス」スタイル

型の概念を適用してServiceLoaderURL 処理を再設計java.net.URLStreamHandlerし、特定のプロトコルの検索/読み込みを行いたいとします。

クラスパスでサービスをレイアウトする方法は次のとおりです。

  • META-INF/java.net.URLStreamHandler/foo
  • META-INF/java.net.URLStreamHandler/bar
  • META-INF/java.net.URLStreamHandler/baz

Wherefooは、前と同じようにサービス実装の名前を含むプレーン テキスト ファイルです。誰かがfoo://...URL を作成したとします。次の方法で、その実装をすばやく見つけることができます。

ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");

代替の「サービス」スタイル 2

サービス ファイルに構成情報を入れたいとします。そのため、クラス名だけではありません。サービスをプロパティ ファイルに解決する別のスタイルを次に示します。慣例により、1 つのキーはクラス名になり、他のキーは注入可能なプロパティになります。

ここredにプロパティファイルがあります

  • META-INF/org.acme.Plugin/red
  • META-INF/org.acme.Plugin/blue
  • META-INF/org.acme.Plugin/green

今までと同じように調べることができます。

ResourceFinder finder = new ResourceFinder("META-INF/");

Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName());
Properties redDefinition = plugins.get("red");

xbean-reflectフレームワークフリーの IoC を提供できるもう 1 つの小さなライブラリである でこれらのプロパティを使用する方法を次に示します。クラス名といくつかの名前と値のペアを指定するだけで、構築して注入します。

ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString());
recipe.setAllProperties(redDefinition);

Plugin red = (Plugin) recipe.create();
red.start();

長い形式で「綴られた」ように見える方法は次のとおりです。

ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin");
recipe.setProperty("myDateField","2011-08-29");
recipe.setProperty("myIntField","100");
recipe.setProperty("myBooleanField","true");
recipe.setProperty("myUrlField","http://www.stackoverflow.com");
Plugin red = (Plugin) recipe.create();
red.start();

このxbean-reflectライブラリは、組み込みの JavaBeans API よりも一歩進んでいますが、Guice や Spring などの完全な IoC フレームワークを使用する必要がなければ、少し優れています。ファクトリ メソッド、コンストラクタ引数、セッター/フィールド インジェクションをサポートしています。

ServiceLoader が限定されているのはなぜですか?

JVM の非推奨コードは、Java 言語自体に損害を与えます。多くのものは、JVM に追加される前に骨に合わせてトリミングされます。後でそれらをトリミングすることはできないからです。はそのServiceLoader最たる例です。API は限られており、OpenJDK の実装は javadoc を含めて約 500 行です。

そこには派手なものは何もなく、交換は簡単です。うまくいかない場合は、使用しないでください。

クラスパスのスコープ

API は別として、純粋に実用的には、検索される URL の範囲を狭めることが、この問題の真の解決策です。アプリケーション サーバーには、アプリケーション内の jar を除いて、それ自体で非常に多くの URL があります。たとえば、OSX 上の Tomcat 7 には、StandardClassLoader だけで約 40~ の URL があります (これはすべての webapp クラスローダーの親です)。

アプリ サーバーが大きいほど、単純な検索でも時間がかかります。

複数のエントリを検索する場合、キャッシュは役に立ちません。同様に、それはいくつかの悪いリークを追加する可能性があります. 実際の負け負けのシナリオになる可能性があります。

本当に関心のある 5 つまたは 12 の URL に絞り込むと、あらゆる種類のサービスの読み込みを行うことができ、ヒットに気付くことはありません。

于 2011-08-29T23:28:33.767 に答える
6

使用するクラスローダーを指定できるように、2つの引数バージョンを使用してみましたか?つまり、java.util.ServiceLoader.load(Class, ClassLoader)

于 2011-08-25T23:30:13.440 に答える
4

ム。

1x WebContainer <-> Nx WebApplication システムでは、WebContainer でインスタンス化された ServiceLoader は WebApplications で定義されたクラスを取得せず、コンテナー内のクラスのみを取得します。WebApplication でインスタンス化された ServiceLoader は、コンテナーで定義されたクラスに加えて、アプリケーションで定義されたクラスを検出します。

WebApplications は分離しておく必要があり、そのように設計されていること、それを回避しようとすると物事壊れること、およびそれらはコンテナーを拡張するために利用できる方法とシステムではないことに注意してください-ライブラリが単純な Jar の場合は、ドロップするだけですコンテナーの適切な拡張フォルダーに入れます。

于 2011-08-23T10:38:27.537 に答える
3

コメントに追加したリンクのニールの答えが本当に好きです。最近のプロジェクトで同じ経験があるためです。

「ServiceLoader で心に留めておくべきもう 1 つのことは、ルックアップ メカニズムを抽象化しようとすることです。パブリッシュ メカニズムは非常に優れており、クリーンで宣言的です。しかし、(java.util.ServiceLoader を介した) ルックアップは地獄のように醜く、クラスパスとして実装されています。グローバルな可視性を持たない環境 (OSGi や Java EE など) にコードを配置すると、ひどく壊れるスキャナー. コードがそれに絡まってしまうと、後で OSGi で実行するのに苦労することになります.時が来たら置き換えることができる抽象化を書くことです。」

私は実際にOSGi環境でこの問題に遭遇しましたが、実際には私たちのプロジェクトでは日食にすぎません。しかし、幸いなことに、タイムリーに修正しました。私の回避策は、ロードしたいプラグインの 1 つのクラスを使用し、そこから classLoader を取得することです。それは有効な修正になります。標準の ServiceLoader は使用しませんでしたが、私のプロセスは非常に似ています。プロパティを使用して、ロードする必要があるプラグイン クラスを定義します。また、各プラグインのクラスローダーを知る別の方法があることも知っています。しかし、少なくとも私はそれを使用する必要はありません。

正直なところ、私は ServiceLoader で使用されるジェネリックが好きではありません。1 つの ServiceLoader が 1 つのインターフェースのクラスしか処理できないことが制限されているためです。え、本当に役に立つの?私の実装では、この制限によって強制されることはありません。ローダーの 1 つの実装を使用して、すべてのプラグイン クラスをロードします。2つ以上使う理由がわかりません。コンシューマーは、インターフェースと実装の間の関係について構成ファイルから知ることができるためです。

于 2011-08-16T13:39:38.047 に答える
1

この質問は、私が最初に予想したよりも複雑に思えます。私が見ているように、ServiceLoader を扱うには 3 つの戦略が考えられます。

  1. 静的 ServiceLoader インスタンスを使用し、ServiceLoader 参照を保持しているクラスローダーと同じクラスローダーからのクラスのロードのみをサポートします。これは次の場合に機能します

    • サービスの構成と実装は共有クラスローダーにあり、すべての子クラスローダーは同じ実装を使用しています。ドキュメントの例は、このユース ケースを対象としています。

      または

    • 構成と実装は、各子クラスローダーに配置され、 の各 webapp に沿って展開されWEB-INF/libます。

    このシナリオでは、サービスを共有クラスローダーにデプロイして、各 Web アプリケーションに独自のサービス実装を選択させることはできません。

  2. アクセスごとに ServiceLoader を初期化し、現在のスレッドのコンテキスト クラスローダーを 2 番目のパラメーターとして渡します。このアプローチは、JAXP および JAXB API で採用されていますが、これらはServiceLoader の代わりに独自のFactoryFinder実装を使用しています。そのため、xml パーサーを webapp にバンドルして、たとえば によって自動的に取得することができますDocumentBuilderFactory#newInstance

    このルックアップはパフォーマンスに影響を与えますが、xml 解析の場合、実装をルックアップする時間は、xml ドキュメントを実際に解析するのに必要な時間と比較してわずかです。ライブラリでは、ファクトリ自体が非常に単純であるため、ルックアップ時間がパフォーマンスを支配すると想定しています。

  3. 何らかの方法で、コンテキスト クラスローダーをキーとして実装クラスをキャッシュします。上記のすべてのケースで、メモリ リークを発生させずにこれが可能かどうかは完全にはわかりません。

結論として、私はおそらくこの問題を無視し、上記のオプション 1b のように、ライブラリが各 webapp 内にデプロイされることを要求します。

于 2011-08-28T12:17:19.167 に答える