Dependiendo de sus requisitos particulares, en algunos casos, el mecanismo del cargador de servicios de Java puede lograr lo que busca.
En resumen, permite a los desarrolladores declarar explícitamente que una clase subclasifica alguna otra clase (o implementa alguna interfaz) al incluirla en un archivo en el META-INF/services
directorio del archivo JAR / WAR . Luego se puede descubrir usando la java.util.ServiceLoader
clase que, cuando se le da un Class
objeto, generará instancias de todas las subclases declaradas de esa clase (o, si Class
representa una interfaz, todas las clases que implementan esa interfaz).
La principal ventaja de este enfoque es que no es necesario escanear manualmente todo el classpath en busca de subclases: toda la lógica de descubrimiento está contenida dentro de la ServiceLoader
clase, y solo carga las clases declaradas explícitamente en el META-INF/services
directorio (no todas las clases en el classpath) .
Sin embargo, hay algunas desventajas:
- No encontrará todas las subclases, solo aquellas que se declaren explícitamente. Como tal, si realmente necesita encontrar todas las subclases, este enfoque puede ser insuficiente.
- Requiere que el desarrollador declare explícitamente la clase en el
META-INF/services
directorio. Esta es una carga adicional para el desarrollador y puede ser propensa a errores.
- El
ServiceLoader.iterator()
genera instancias de subclase, no sus Class
objetos. Esto causa dos problemas:
- No tiene nada que decir sobre cómo se construyen las subclases: el constructor sin argumentos se utiliza para crear las instancias.
- Como tal, las subclases deben tener un constructor predeterminado o declarar explícitamente un constructor sin argumentos.
Aparentemente, Java 9 abordará algunas de estas deficiencias (en particular, las relacionadas con la creación de instancias de subclases).
Un ejemplo
Supongamos que está interesado en encontrar clases que implementen una interfaz com.example.Example
:
package com.example;
public interface Example {
public String getStr();
}
La clase com.example.ExampleImpl
implementa esa interfaz:
package com.example;
public class ExampleImpl implements Example {
public String getStr() {
return "ExampleImpl's string.";
}
}
Declararía que la clase ExampleImpl
es una implementación Example
al crear un archivo que META-INF/services/com.example.Example
contenga el texto com.example.ExampleImpl
.
Luego, puede obtener una instancia de cada implementación de Example
(incluida una instancia de ExampleImpl
) de la siguiente manera:
ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
System.out.println(example.getStr());
}
// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.