¿Puedo hacerlo con reflexión o algo así?
¿Puedo hacerlo con reflexión o algo así?
Respuestas:
He estado buscando por un tiempo y parece que hay diferentes enfoques, aquí hay un resumen:
La biblioteca de reflexiones es bastante popular si no te importa agregar la dependencia. Se vería así:
Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
ServiceLoader (según la respuesta de Erickson ) y se vería así:
ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
for (Pet implClass : loader) {
System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
}
Tenga en cuenta que para que esto funcione, debe definir Pet
como ServiceProviderInterface (SPI) y declarar sus implementaciones. lo hace creando un archivo resources/META-INF/services
con el nombre examples.reflections.Pet
y declarando todas las implementaciones de Pet
en él
examples.reflections.Dog
examples.reflections.Cat
anotación a nivel de paquete . Aquí hay un ejemplo:
Package[] packages = Package.getPackages();
for (Package p : packages) {
MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
if (annotation != null) {
Class<?>[] implementations = annotation.implementationsOfPet();
for (Class<?> impl : implementations) {
System.out.println(impl.getSimpleName());
}
}
}
y la definición de la anotación:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
public @interface MyPackageAnnotation {
Class<?>[] implementationsOfPet() default {};
}
y debe declarar la anotación a nivel de paquete en un archivo llamado package-info.java
dentro de ese paquete. aquí hay contenidos de muestra:
@MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
package examples.reflections;
Tenga en cuenta que solo los paquetes que son conocidos por ClassLoader en ese momento se cargarán mediante una llamada a Package.getPackages()
.
Además, existen otros enfoques basados en URLClassLoader que siempre estarán limitados a las clases que ya se han cargado, a menos que realice una búsqueda basada en directorios.
Lo que dijo Erickson, pero si aún quieres hacerlo, echa un vistazo a Reflections . De su página:
Con Reflections puede consultar sus metadatos para:
- obtener todos los subtipos de algún tipo
- Obtenga todos los tipos anotados con alguna anotación
- Obtenga todos los tipos anotados con alguna anotación, incluidos los parámetros de anotación que coinciden
- obtener todos los métodos anotados con algunos
new Reflections("my.package").getSubTypesOf(MyInterface.class)
En general, es caro hacer esto. Para usar la reflexión, la clase debe estar cargada. Si desea cargar todas las clases disponibles en la ruta de clases, eso llevará tiempo y memoria, y no se recomienda.
Si desea evitar esto, deberá implementar su propio analizador de archivos de clase que funcione de manera más eficiente, en lugar de la reflexión. Una biblioteca de ingeniería de código de bytes puede ayudar con este enfoque.
El mecanismo del proveedor de servicios es el medio convencional para enumerar las implementaciones de un servicio conectable y se ha vuelto más establecido con la introducción de Project Jigsaw (módulos) en Java 9. Utilice ServiceLoader
en Java 6, o implemente el suyo en versiones anteriores. Proporcioné un ejemplo en otra respuesta.
META-INF/services/java.sql.Driver
Spring tiene una forma bastante simple de lograr esto:
public interface ITask {
void doStuff();
}
@Component
public class MyTask implements ITask {
public void doStuff(){}
}
Luego, puede conectar automáticamente una lista de tipos ITask
y Spring la completará con todas las implementaciones:
@Service
public class TaskService {
@Autowired
private List<ITask> tasks;
}
El mecanismo más robusto para enumerar todas las clases que implementan una interfaz dada es actualmente ClassGraph , porque maneja la más amplia gama posible de mecanismos de especificación de rutas de clases , incluido el nuevo sistema de módulos JPMS. (Yo soy el autor)
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
foundImplementingClass(ci); // Do something with the ClassInfo object
}
}
Sí, el primer paso es identificar "todas" las clases que le interesan. Si ya tiene esta información, puede enumerar cada uno de ellos y usar instanceof para validar la relación. Un artículo relacionado está aquí: http://www.javaworld.com/javaworld/javatips/jw-javatip113.html
Lo que dijo Erikson es lo mejor. Aquí hay un hilo de preguntas y respuestas relacionado: http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html
La biblioteca Apache BCEL le permite leer clases sin cargarlas. Creo que será más rápido porque debería poder omitir el paso de verificación. El otro problema con la carga de todas las clases usando el cargador de clases es que sufrirá un gran impacto en la memoria y ejecutará inadvertidamente cualquier bloque de código estático que probablemente no desee hacer.
El enlace de la biblioteca Apache BCEL: http://jakarta.apache.org/bcel/
Con ClassGraph es bastante simple:
Código maravilloso para encontrar implementaciones de my.package.MyInterface
:
@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}
scan().withCloseable { ... }
a Groovy, o usar try-with-resources en Java: github.com/classgraph/classgraph/wiki/… Además, la última parte debería ser .name
, no .className
, ya que .getName()
es el método correcto para obtener el nombre de una clase de un ClassInfo
objeto.
Una nueva versión de la respuesta de @ kaybee99, pero ahora devuelve lo que el usuario pregunta: las implementaciones ...
Spring tiene una forma bastante simple de lograr esto:
public interface ITask {
void doStuff();
default ITask getImplementation() {
return this;
}
}
@Component
public class MyTask implements ITask {
public void doStuff(){}
}
Luego, puede conectar automáticamente una lista de tipos ITask
y Spring la completará con todas las implementaciones:
@Service
public class TaskService {
@Autowired(required = false)
private List<ITask> tasks;
if ( tasks != null)
for (ITask<?> taskImpl: tasks) {
taskImpl.doStuff();
}
}
Me encontré con el mismo problema. Mi solución fue usar la reflexión para examinar todos los métodos en una clase ObjectFactory, eliminando aquellos que no eran métodos createXXX () devolviendo una instancia de uno de mis POJO enlazados. Cada clase así descubierta se agrega a una matriz Class [], que luego se pasa a la llamada de instanciación JAXBContext. Esto funciona bien, solo necesita cargar la clase ObjectFactory, que estaba a punto de ser necesaria de todos modos. Solo necesito mantener la clase ObjectFactory, una tarea realizada a mano (en mi caso, porque comencé con POJOs y usé schemagen), o puede ser generada según sea necesario por xjc. De cualquier manera, es eficaz, simple y eficaz.