Escaneo de anotaciones de Java en tiempo de ejecución [cerrado]

253

¿Cuál es la mejor manera de buscar en todo el classpath una clase anotada?

Estoy haciendo una biblioteca y quiero permitir que los usuarios anoten sus clases, por lo que cuando se inicia la aplicación web, necesito escanear toda la ruta de clase para obtener cierta anotación.

¿Conoces una biblioteca o una instalación de Java para hacer esto?

Editar: Estoy pensando en algo como la nueva funcionalidad para Java EE 5 Web Services o EJB's. Anota tu clase con @WebServiceo @EJBy el sistema encuentra estas clases mientras se carga para que sean accesibles de forma remota.

Alotor
fuente

Respuestas:

210

Utilice org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Un proveedor de componentes que escanea el classpath desde un paquete base. Luego aplica excluir e incluir filtros a las clases resultantes para encontrar candidatos.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
Arthur Ronald
fuente
55
Gracias por la información. ¿Sabes también cómo explorar classpath en busca de clases cuyos campos tengan anotaciones personalizadas?
Javatar
66
@Javatar Use la API de reflexión de Java. <YOUR_CLASS> .class.getFields () Para cada campo, invoque getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald el
1
NOTA: Si está haciendo esto dentro de una aplicación Spring, Spring seguirá evaluando y actuando en base a @Conditionalanotaciones. Entonces, si una clase tiene un @Conditionalvalor que devuelve falso, no será devuelto findCandidateComponents, incluso si coincide con el filtro del escáner. Esto me sorprendió hoy: terminé usando la solución de Jonathan a continuación.
Adam Burley
1
@ Arthur Ortiz Lo siento, Arthur. Quiero decir que el BeanDefinitionobjeto no proporciona una forma de obtener la clase directamente. Lo más parecido parece ser getBeanClassNameque devuelve un nombre de clase totalmente calificado, pero el comportamiento exacto de este método no está claro. Además, no está claro en qué cargador de clases se encontró la clase.
Max
3
@Max Pruebe esto: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins el
149

Y otra solución son las reflexiones de Google .

Revisión rápida:

  • La solución Spring es el camino a seguir si está utilizando Spring. De lo contrario, es una gran dependencia.
  • Usar ASM directamente es un poco engorroso.
  • Usar Java Assist directamente también es torpe.
  • Annovention es súper ligero y conveniente. No hay integración de Maven todavía.
  • Las reflexiones de Google atraen a las colecciones de Google. Indexa todo y luego es súper rápido.
Jonathan
fuente
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). c'est tout.
zapp
44
¿Necesito especificar un nombre de paquete? ¿comodín? ¿Qué hay para todas las clases en classpath?
Sunnyday
1
Tenga en cuenta que todavía no tiene progreso en el soporte de Java 9: github.com/ronmamo/reflections/issues/186
Vadzim
la biblioteca org.reflections no funciona en Java 13 (quizás también antes). La primera vez que se llama parece estar bien. las instancias y usos posteriores fallan al decir que las URL de búsqueda no están configuradas.
Evvo
44

Puede encontrar clases con cualquier anotación dada con ClassGraph , así como buscar otros criterios de interés, por ejemplo, clases que implementan una interfaz determinada. (Descargo de responsabilidad, soy el autor de ClassGraph.) ClassGraph puede construir una representación abstracta de todo el gráfico de clase (todas las clases, anotaciones, métodos, parámetros de métodos y campos) en la memoria, para todas las clases en el classpath, o para las clases en paquetes en la lista blanca, y puede consultar ese gráfico de clase como desee. ClassGraph admite más mecanismos de especificación de classpath y cargadores de clases que cualquier otro escáner, y también funciona a la perfección con el nuevo sistema de módulos JPMS, por lo que si basa su código en ClassGraph, su código será máximamente portátil. Vea la API aquí.

Luke Hutchison
fuente
1
¿Esto necesita Java 8 para ejecutarse?
David George
1
Actualizado para usar Java7, no hay problema. Simplemente elimine las anotaciones y convierta las funciones para usar clases internas anónimas. Me gusta el estilo de 1 archivo. El código es agradable y limpio, por lo que, aunque no admite algunas cosas que me gustaría (clase + anotación al mismo tiempo), creo que sería bastante fácil de agregar. ¡Buen trabajo! Si alguien no puede hacer el trabajo de modificación para v7, probablemente debería hacerlo Reflections. Además, si está usando guayaba / etc. y desea cambiar las colecciones, es fácil. Grandes comentarios en el interior también.
Andrew Backer
2
@Alexandros gracias, deberías echar un vistazo a ClassGraph, se ha mejorado significativamente con respecto a FastClasspathScanner.
Luke Hutchison
2
@AndrewBacker ClassGraph (la nueva versión de FastClasspathScanner) tiene soporte completo para operaciones booleanas, a través de filtros u operaciones de configuración. Vea ejemplos de código aquí: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison
1
@Luke Hutchison Ya está usando ClassGraph. Me ayudó con la migración a Java 10. Biblioteca realmente útil.
Alexandros
25

Si desea un peso realmente ligero (sin dependencias, API simple, archivo jar de 15 kb) y una solución muy rápida , eche un vistazo annotation-detectora https://github.com/rmuller/infomas-asl

Descargo de responsabilidad: soy el autor.

rmuller
fuente
20

Puede usar la API de procesamiento de anotación conectable de Java para escribir un procesador de anotación que se ejecutará durante el proceso de compilación y recopilará todas las clases anotadas y creará el archivo de índice para el uso en tiempo de ejecución.

Esta es la forma más rápida posible de hacer un descubrimiento de clase anotado porque no necesita escanear su classpath en tiempo de ejecución, que generalmente es una operación muy lenta. Además, este enfoque funciona con cualquier cargador de clases y no solo con URLClassLoaders, generalmente compatibles con escáneres de tiempo de ejecución.

El mecanismo anterior ya está implementado en la biblioteca ClassIndex .

Para usarlo, anote su anotación personalizada con @IndexAnotaciones anotadas . Esto creará en tiempo de compilación un archivo de índice: META-INF / annotations / com / test / YourCustomAnnotation que enumera todas las clases anotadas. Puede acceder al índice en tiempo de ejecución ejecutando:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
Sławek
fuente
5

¿Es demasiado tarde para responder? Yo diría que es mejor ir por bibliotecas como ClassPathScanningCandidateComponentProvider o como Scannotations

Pero incluso después de que alguien quiera probarlo con classLoader, he escrito algunos por mi cuenta para imprimir las anotaciones de las clases en un paquete:

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

Y en el archivo de configuración, coloca el nombre del paquete y lo desmarca a una clase.

kenobiwan
fuente
3

Con Spring también puede escribir lo siguiente usando la clase AnnotationUtils. es decir:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

Para obtener más detalles y todos los métodos diferentes, consulte los documentos oficiales: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

magiccrafter
fuente
44
Buena idea, pero si coloca un nullvalor como el segundo parámetro (que define la clase, en la cual la jerarquía de herencia Spring buscará una clase que use la Anotación), siempre obtendrá un nullretorno, de acuerdo con la implementación.
jonashackt
3

Hay un comentario maravilloso de zapp que incluye todas esas respuestas:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
Zon
fuente
2

La API de Classloader no tiene un método de "enumeración", porque la carga de clases es una actividad "a pedido": generalmente tiene miles de clases en su classpath, de las cuales solo se necesitará una fracción (rt.jar ¡solo 48MB hoy en día!).

Entonces, incluso si pudieras enumerar todas las clases, esto consumiría mucho tiempo y memoria.

El enfoque simple es enumerar las clases en cuestión en un archivo de configuración (xml o lo que más le convenga); Si desea hacer esto automáticamente, limítese a un JAR o un directorio de clase.

mfx
fuente
2

Reflexión de Google si también quieres descubrir interfaces.

Spring ClassPathScanningCandidateComponentProviderno está descubriendo interfaces.

madhu_karnati
fuente
1

Google Reflections parece ser mucho más rápido que Spring. Encontré esta solicitud de función que aborda esta diferencia: http://www.opensaga.org/jira/browse/OS-738

Esta es una razón para usar Reflections ya que el tiempo de inicio de mi aplicación es realmente importante durante el desarrollo. Reflexiones también parece ser muy fácil de usar para mi caso de uso (encontrar todos los implementadores de una interfaz).

Martin Aubele
fuente
1
Si nos fijamos en el problema de JIRA, hay comentarios allí que se alejaron de Reflections debido a problemas de estabilidad.
Wim Deblauwe
1

Si está buscando una alternativa a las reflexiones, me gustaría recomendar Panda Utilities - AnnotationsScanner . Es un escáner libre de guayaba (guayaba tiene ~ 3 MB, Panda Utilities tiene ~ 200 kb) basado en el código fuente de la biblioteca de reflexiones.

También está dedicado a búsquedas futuras. Si desea escanear varias veces las fuentes incluidas o incluso proporcionar una API, que permite a alguien escanear la ruta de clase actual, AnnotationsScannerProcessalmacena en caché todas las recuperadas ClassFiles, por lo que es realmente rápido.

Ejemplo simple de AnnotationsScanneruso:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
dzikoysk
fuente
1

La primavera tiene algo llamado AnnotatedTypeScannerclase.
Esta clase usa internamente

ClassPathScanningCandidateComponentProvider

Esta clase tiene el código para el escaneo real de los recursos de classpath . Para ello, utiliza los metadatos de clase disponibles en tiempo de ejecución.

Simplemente se puede extender esta clase o usar la misma clase para escanear. A continuación se muestra la definición del constructor.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
swayamraina
fuente