问题
I've stumbled upon some inconvenience the other day using java.util.ServiceLoader
and some questions formed in me.
Suppose I have a generic service:
public interface Service<T> { ... }
I couldn't explicitly tell ServiceLoader
to load only implementations with a specific generic type.
ServiceLoader<Service<String>> services =
ServiceLoader.load(Service.class); // Fail.
My question is: what are reasonable ways to use ServiceLoader
to safely load implementations of a generic service?
After asking the above question and before Paŭlo's answer I've managed to come up with a solution.
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!");
}
Changing boolean canServe(Class<?> t)
to boolean canServe(Object o)
and also changing <T> Service<T> getService(Class<?> t)
in the same manner can be more dynamic (I'm using the latter for myself as I had a method boolean canHandle(T t)
on my interface in the beginning.)
回答1:
The problem here is that the service loader is using a file listing all implementations of a given class/interface, the file being named by the interfaces name. It was not foreseen to put the type parameter into this file name, and it also is not really possible to pass generic types as Class
objects.
So, you here can only get your generic services of any types, and then inspect their class object to see if it is a subtype of Service<String>
.
Something like this:
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;
}
}
This is not tested, and may need to be extended to also search interfaces extended by the interfaces our class implements directly, and interfaces implemented by our (generic) superclass.
And of course, this can only find classes like
class Example implements Service<String> { ...}
not something like
class Example<X> implements Service<X> { ... }
where Example<String>
might be a valid implementation of your service.
回答2:
You could also just copy the ServiceLoader class file and remove the generic type argument from the load() method, causing it to always work. You'll just need to override the warnings.
public static <S> ServiceLoader load(final Class<S> service)
{
return load(service, Thread.currentThread().getContextClassLoader());
}
来源:https://stackoverflow.com/questions/5451734/loading-generic-service-implementations-via-java-util-serviceloader