2011-03-27 12 views
6

He tropezado con algún inconveniente el otro día usando java.util.ServiceLoader y algunas preguntas formadas en mí.Cargando implementaciones de servicios genéricos a través de java.util.ServiceLoader

Supongamos que tengo un servicio genérico:

public interface Service<T> { ... } 

No podría decir de manera explícita ServiceLoader para cargar sólo implementaciones con un tipo genérico específico.

ServiceLoader<Service<String>> services = 
    ServiceLoader.load(Service.class); // Fail. 

Mi pregunta es: ¿cuáles son formas razonables para utilizar ServiceLoader para cargar con seguridad puestas en práctica de un servicio genérico?


Después de hacer la pregunta anterior y antes de la respuesta de Paŭlo, he logrado encontrar una solución.

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!"); 
    } 

Cambio boolean canServe(Class<?> t)-boolean canServe(Object o) y también cambiando <T> Service<T> getService(Class<?> t) de la misma manera puede ser más dinámico (estoy usando esta última para mí, ya que tenía un método boolean canHandle(T t) en mi interfaz en el principio.)

Respuesta

6

El problema aquí es que el cargador de servicios está utilizando un archivo que enumera todas las implementaciones de una clase/interfaz determinada, el nombre del nombre de las interfaces. No estaba previsto colocar el parámetro tipo en este nombre de archivo, y tampoco es posible pasar tipos genéricos como objetos Class.

Por lo tanto, aquí solo puede obtener sus servicios genéricos de cualquier tipo, y luego inspeccionar su objeto de clase para ver si es un subtipo de Service<String>.

Algo como esto:

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; 
    } 

} 

Esto no se ha probado, y puede ser necesario extender a buscar también las interfaces extendidas por las interfaces de nuestra clase implementa directamente, e interfaces implementadas por nuestra superclase (genérico).

Y, por supuesto, esto sólo puede encontrar clases como

class Example implements Service<String> { ...} 
no

algo así como

class Example<X> implements Service<X> { ... } 

donde Example<String> podría ser una aplicación válida de su servicio.

+0

Gracias! Su solución * podría * funcionar dados los detalles que he presentado. Sin embargo, no lo probé (todavía), porque se me ocurrió una solución * elegante * para el proyecto en el que estoy trabajando. He editado mi pregunta para presentar mi solución. Además, en una nota al margen: ¿sería demasiado complicado implementar algo como esto? 'META-INF/services/com.example.Service' donde el' com.example.El archivo Service' contendría algo como esto: ' = com.example.impl.StringService = com.example.impl.DoubleService' o algo más elaborado. –

+0

No es demasiado complicado, pero tendrá que hacerlo usted mismo. (Sin embargo, use otro directorio para no entrar en conflicto con los archivos que utiliza 'ServiceLoader'). Una vez he vuelto a implementar el mecanismo 'ServiceLoader' para 1.5, pero luego descubro que otras partes de mi programa necesitan 1.6 de todos modos, entonces ' lo he tirado. –

0

También podría simplemente copiar el archivo de clase ServiceLoader y eliminar el argumento de tipo genérico del método load(), haciendo que siempre funcione. Solo tendrá que anular las advertencias.

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