Loading generic service implementations via java.util.ServiceLoader

与世无争的帅哥 提交于 2021-01-20 16:08:32

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!