¿Cómo puedo obtener una lista de todas las implementaciones de una interfaz mediante programación en Java? [cerrado]

81

¿Puedo hacerlo con reflexión o algo así?

user2427
fuente
¿Es posible hacerlo con algún atajo mágico de Eclipse? ?
Tomasz Waszczyk
4
Considere seleccionar una respuesta.
Please_Dont_Bully_Me_SO_Lords

Respuestas:

56

He estado buscando por un tiempo y parece que hay diferentes enfoques, aquí hay un resumen:

  1. 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);
    
  2. 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
    
  3. 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.javadentro 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.

Ahmad Abdelghany
fuente
¿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
  • obtener todos los métodos anotados con algunos
Peter Severin
fuente
12
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.

erickson
fuente
4
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:

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Luego, puede conectar automáticamente una lista de tipos ITasky Spring la completará con todas las implementaciones:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}
kaybee99
fuente
4
No exactamente, Spring lo completará con todos los beans de tipo ITask, que no es exactamente lo mismo.
Olivier Gérardin
5

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
    }
}
Luke Hutchison
fuente
1
La biblioteca ClassGraph funciona como un sharm, gracias @Luke Hutchison
Kyrylo Semenko
4

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
4

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
}
verglor
fuente
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:

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 ITasky 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();
    }   
}
Please_Dont_Bully_Me_SO_Lords
fuente
1

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.

Uri
fuente
1

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.

NDK
fuente