使用する際の最善策は何ですか?サービスローダー複数の ClassLoader を持つ環境では、ドキュメントでは初期化時に単一のサービス インスタンスを作成して保存することを推奨しています。
private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);
これにより、現在のコンテキスト クラスローダーを使用して ServiceLoader が初期化されます。ここで、このスニペットが Web コンテナー内の共有クラスローダーを使用してロードされたクラスに含まれており、複数の Web アプリケーションが独自のサービス実装を定義したいとします。これらは上記のコードでは取得されず、ローダーが最初の Web アプリケーション コンテキスト クラスローダーを使用して初期化され、他のユーザーに間違った実装が提供される可能性もあります。
常に新しい ServiceLoader を作成すると、毎回サービス ファイルを列挙して解析する必要があるため、パフォーマンスの面で無駄が多いようです。編集:これは、次の図に示すように、大きなパフォーマンスの問題となる可能性があります。JavaのXPath実装に関するこの回答。
他のライブラリはこれをどのように処理しますか? クラスローダーごとに実装をキャッシュしますか、毎回構成を再解析しますか、それとも単にこの問題を無視して 1 つのクラスローダーに対してのみ機能しますか?
ベストアンサー1
私は個人的に、いかなる状況でも が好きではありませんServiceLoader
。 遅くて無駄が多く、最適化できる方法はほとんどありません。
また、少し制限があるようにも思います。タイプだけで検索する以上のことをしたい場合は、本当に手間がかかります。
xbean-finder の 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();
代替の「サービス」スタイル
たとえば、ServiceLoader
タイプ概念を適用して URL 処理を再設計し、java.net.URLStreamHandler
特定のプロトコルの を検索/読み込みたいとします。
クラスパス内のサービスをレイアウトする方法は次のとおりです。
META-INF/java.net.URLStreamHandler/foo
META-INF/java.net.URLStreamHandler/bar
META-INF/java.net.URLStreamHandler/baz
はfoo
、以前と同じように、サービス実装の名前を含むプレーンテキスト ファイルです。ここで、誰かが URL を作成したとしますfoo://...
。次の方法で、その実装をすばやく見つけることができます。
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");
フレームワークフリーの IoC を提供できる別の小さなライブラリでこれらのプロパティを使用する方法を次に示しますxbean-reflect
。クラス名といくつかの名前と値のペアを指定するだけで、構築と挿入が行われます。
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 (これはすべての Web アプリケーション クラスローダーの親です) だけで約 40 個の URL があります。
アプリケーション サーバーが大きくなるほど、単純な検索でも時間がかかります。
複数のエントリを検索する場合、キャッシュは役に立ちません。また、悪質なリークが発生する可能性もあります。これは、まさに両者にとって損失となるシナリオです。
本当に重要な URL を 5 つまたは 12 個に絞り込むと、あらゆる種類のサービスの読み込みを実行してもヒットに気付くことはありません。