Download generic services using java.util.ServiceLoader

The other day I came across some inconvenience using java.util.ServiceLoader , and some questions formed in me.

Suppose I have a shared service:

 public interface Service<T> { ... } 

I could not 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 securely load shared service implementations?


After you asked the above question and before Paŭlo answered, I managed to find 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) , as well as changing <T> Service<T> getService(Class<?> t) in the same way can be more dynamic (I use the latter for myself, since at the beginning I had the boolean canHandle(T t) method on my interface.)

+6
java java-6
source share
2 answers

The problem is that the service loader uses a file that lists all implementations of the given class / interface, and the file is named after the interface. It was not proposed to enter a type parameter into this file name, and it is also not possible to pass generic types as Class objects.

So, you can only get your shared services of any type, and then check their class object to determine if it is a subtype of Service<String> .

Something like that:

 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 has not been tested and may need to be expanded to also look for interfaces extended by the interfaces that our class implements directly, and interfaces implemented by our (universal) 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> may be a valid version of your service.

+6
source share

You can also just copy the file of the ServiceLoader class and remove the generic type argument from the load () method, making it always work. You just need to cancel the warnings.

  public static <S> ServiceLoader load(final Class<S> service) { return load(service, Thread.currentThread().getContextClassLoader()); } 
0
source share

All Articles