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 Petcomo ServiceProviderInterface (SPI) y declarar sus implementaciones. lo hace creando un archivo resources/META-INF/servicescon el nombre examples.reflections.Pety declarando todas las implementaciones de Peten é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());
}
}
}
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.
¿Cuál de los tres enfoques es más eficiente y rápido?
carlspring
@carlspring No puedo hacer una declaración absoluta sobre su eficiencia relativa, pero la primera alternativa (biblioteca de reflexiones) funcionó bastante bien para mí.
Ahmad Abdelghany
¿Cómo funciona DriverManager de JDBC? ¿No está haciendo algo similar (buscando todas las implementaciones de la interfaz del controlador en classpath)?
Alex Semeniuk
1
@AlexSemeniuk Creo que ahora admiten el mecanismo Service Loader / Provider (enfoque n. ° 2 anterior) de acuerdo con sus documentos "Los métodos DriverManager getConnection y getDrivers se han mejorado para admitir el mecanismo del proveedor de servicios Java Standard Edition". Ver docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
Ahmad Abdelghany
1
Vale la pena señalar que el mecanismo del proveedor de servicios se ha integrado en el sistema del módulo con Java 9, lo que lo hace aún más conveniente que las anotaciones a nivel de paquete, es decir, no necesita crear su propio tipo de anotación, puede declarar las implementaciones en el módulo -info que creará de todos modos al escribir software modular y obtendrá comentarios en tiempo de compilación sobre la validez de la implementación.
Holger
28
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
más específico:new Reflections("my.package").getSubTypesOf(MyInterface.class)
zapp
27
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 ServiceLoaderen Java 6, o implemente el suyo en versiones anteriores. Proporcioné un ejemplo en otra respuesta.
Desafortunadamente, el mecanismo del proveedor de servicios requiere que enumere las clases que podrían estar en la lista de interés en un archivo separado que a menudo es inviable.
averasko
4
Es factible escribir código fuente implementando una interfaz, compilarlo e implementarlo, pero ¿no es factible incluir un archivo de texto con el FQN de la implementación? Rara vez, si es que alguna vez, es el caso. Apenas "a menudo".
erickson
¿Cómo funciona DriverManager de JDBC? ¿No está haciendo algo similar (buscando todas las implementaciones de la interfaz del controlador en classpath)?
Alex Semeniuk
1
@AlexSemeniuk No. utiliza el mecanismo Service Loader que describo anteriormente; Un controlador JDBC 4.0+ debe incluir su nombre enMETA-INF/services/java.sql.Driver
erickson
14
Spring tiene una forma bastante simple de lograr esto:
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
}
}
La biblioteca ClassGraph funciona como un sharm, gracias @Luke Hutchison
Kyrylo Semenko
4
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
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.
Debe llamar 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 ClassInfoobjeto.
Luke Hutchison
2
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:
Además, si está escribiendo un complemento IDE (donde lo que intenta hacer es relativamente común), entonces el IDE generalmente le ofrece formas más eficientes de acceder a la jerarquía de clases del estado actual del código de usuario.
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.
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 archivoresources/META-INF/services
con el nombreexamples.reflections.Pet
y declarando todas las implementaciones dePet
en élanotació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.
fuente
Lo que dijo Erickson, pero si aún quieres hacerlo, echa un vistazo a Reflections . De su página:
fuente
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.fuente
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; }
fuente
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 } }
fuente
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
fuente
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/
fuente
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 }
fuente
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 unClassInfo
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(); } }
fuente
Además, si está escribiendo un complemento IDE (donde lo que intenta hacer es relativamente común), entonces el IDE generalmente le ofrece formas más eficientes de acceder a la jerarquía de clases del estado actual del código de usuario.
fuente
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.
fuente