Javaを介した一般的なサービス実装のロード。ウティルServiceLoader
質問
私は先日、いくつかの不便に遭遇しました java.util.ServiceLoader
そして、いくつかの質問が私の中で形成されました。
私が一般的なサービスを持っているとします:
public interface Service<T> { ... }
私は明示的に言うことができませんでした ServiceLoader
ロードするには のみ 特定のジェネリック型を持つ実装。
ServiceLoader<Service<String>> services =
ServiceLoader.load(Service.class); // Fail.
私の質問は:合理的な使い方は何ですか ServiceLoader
汎用サービスの実装を安全にロードするには?
上記の質問をした後、Paúloの答えの前に、私は解決策を考え出すことができました。
public interface Service<T> { ...
// true if an implementation can handle the given `t' type; false otherwise.
public boolean canHandle(Class<?> t) { ...
public final class StringService implements Service<String> { ...
@Override public boolean canHandle(Class<?> t) {
if (String.class.isAssignableFrom(type))
return true;
return false;
}
public final class DoubleService implements Service<Double> { ...
// ...
public final class Services { ...
public static <T> Service<T> getService(Class<?> t) {
for (Service<T> s : ServiceLoader.load(Service.class))
if (s.canServe(t))
return s;
throw new UnsupportedOperationException("No servings today my son!");
}
変化する boolean canServe(Class<?> t)
に boolean canServe(Object o)
また、変化する <T> Service<T> getService(Class<?> t)
同じように、より動的にすることができます(私はメソッドを持っていたので、私は自分自身のために後者を使用しています boolean canHandle(T t)
最初の私のインターフェイスで。)
解決
ここでの問題は、サービスローダーが指定されたクラス/インターフェイスのすべての実装をリストするファイルを使用しており、ファイルはインターフェイス名で命名されていることです。このファイル名にtypeパラメータを入れることは予測されていませんでしたが、ジェネリック型を次のように渡すことも実際には不可能です Class
オブジェクト。
したがって、ここでは、任意のタイプの汎用サービスのみを取得し、クラスオブジェクトを調べて、それがサブタイプであるかどうかを確認できます Service<String>
.
このようなもの:
class Test{
public Service<String> getStringService() {
// it is a bit strange that we can't explicitely construct a
// parametrized type from raw type and parameters, so here
// we use this workaround. This may need a dummy method or
// variable if this method should have another return type.
ParametrizedType stringServiceType =
(ParametrizedType)Test.class.getMethod("getStringService").getGenericReturnType();
ServiceLoader<Service<?>> loader = ServiceLoader.load(Service<?>.class);
for(Service<?> service : loader) {
if(isImplementing(service.getClass(), stringServiceType)) {
@SuppressWarnings("unchecked")
Service<String> s = (Service)service;
return s;
}
}
}
public boolean isImplementing(Class<?> candidate, ParametrizedType t) {
for(Type iFace : candidate.getGenericInterfaces()) {
if(iFace.equals(t)) {
return true;
}
if(iFace instanceof ParametrizedType &&
((ParametrizedType)iFace).getRawType().equals(t.getRawType())) {
return false;
}
}
return false;
}
}
これはテストされておらず、クラスが直接実装するインターフェイスによって拡張されたインターフェイスと、(汎用)スーパークラスによって実装されたインターフェイスを検索するために拡張する必要があるかもしれません。
そしてもちろん、これは次のようなクラスしか見つけることができません
class Example implements Service<String> { ...}
のようなものではありません
class Example<X> implements Service<X> { ... }
ここで Example<String>
サービスの有効な実装である可能性があります。
他のヒント
ServiceLoaderクラスファイルをコピーして、load()メソッドからgeneric型引数を削除するだけで、常に機能する。あなたはただ警告を上書きする必要があります。
public static <S> ServiceLoader load(final Class<S> service)
{
return load(service, Thread.currentThread().getContextClassLoader());
}
.