2008-11-03 13 views
198

¿Cuál es la mejor forma de buscar todo el classpath para una clase anotada?Escaneo de anotaciones Java en el tiempo de ejecución

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

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

Editar: Estoy pensando en algo así como la nueva funcionalidad para Java EE 5 Web Services o EJB. Anota tu clase con @WebService o @EJB y el sistema encuentra estas clases durante la carga para que se pueda acceder de forma remota.

Respuesta

163

Uso org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Un proveedor de componentes que escanea la ruta de clase de un paquete básico. Luego aplica exclusión e incluye filtros en 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()); 
+4

Gracias por su información. ¿También sabe cómo escanear classpath para las clases cuyos campos tienen una anotación personalizada? – Javatar

+6

@Javatar Utilice la API de reflexión de Java. .class.getFields() Para cada campo, invoque getAnnotation () –

+0

@ArthurRonaldFDGarcia, ¿cómo puedo escanear 'sub package' de esa manera? – CycDemo

1

Java no tiene "Descubrimiento". La única forma que conozco es escanear el directorio en el que deberían estar los archivos .class, analizar los nombres y usar eso. Horriblemente feo, tal vez hay un paquete mejor en estos días, no lo he visto en unos años.

Normalmente este problema solía abordarse al incluir un archivo de propiedades o un archivo .xml con los nombres de clase en él.

Estaría interesado en escuchar una mejor respuesta también.

3

Un poco offtopic, pero Spring también hace algo similar, usando <context:component-scan>, ¿cuál podría estudiar el código fuente?

Spring proporciona la capacidad de detectar automáticamente clases "estereotipadas" [...]. Para autodetectar estas clases y registrar los beans correspondientes, se requiere la inclusión de [context: component-scan element].

1

La API de cargador de clases no tiene un método de "enumerar", debido a la carga de clases es una actividad "on-demand" - por lo general tienen miles de clases en la ruta de clases, sólo una fracción de lo que alguna vez necesario (¡el rt.jar solo es 48MB hoy en día!).

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

El enfoque simple es listar las clases afectadas en un archivo de instalación (xml o lo que sea que se ajuste a su fantasía); si desea hacer esto automáticamente, restrinja a un JAR o directorio de una clase.

2

No estoy seguro de si te ayudará o no, pero podrías echarle un vistazo al proyecto Apache commons-discovery.

discovery project

10

Uso del ServiceLoader, o implement your own si no está en Java 6.

Tal vez un procesador de anotación podría producir los archivos necesarios en virtud de META-INF/services en tiempo de compilación.

6

Probar Scannotation.

Se puede utilizar para buscar el classpath o el directorio lib de su aplicación web para anotaciones específicas.

+0

-1 Parece que esta biblioteca no está actualizada, usa una versión muy antigua de javassist que no puede leer los 8 archivos de clase java. Use "reflexiones" en su lugar (vea la respuesta de Jonathan) –

133

Y Otra solución es Google reflections.

revisión rápida:

  • solución de primavera es el camino a seguir si usted está utilizando la primavera. De lo contrario, es una gran dependencia.
  • El uso de ASM directamente es un poco engorroso.
  • El uso de Java Assist directamente también es torpe.
  • La annovención es súper liviana y conveniente. No hay integración de maven todavía.
  • Reflejos de Google extraen colecciones de Google. Indexa todo y luego es súper rápido.
+31

' new Reflections ("my.package"). getTypesAnnotatedWith (MyAnnotation.class) '. c'est tout. – zapp

+0

perfecto para escaneo de propósito general, ty! – gibffe

+0

¿necesito especificar un nombre de paquete? ¿comodín? que para todas las clases en classpath? – Sunnyday

17

Si quieres un peso ligero (sin dependencias, API simple, 15 kb archivo jar) solución y muy rápido, echar un vistazo a annotation-detector se encuentra en https://github.com/rmuller/infomas-asl

Descargo de responsabilidad: soy el autor.

13

Puede utilizar Java Pluggable Annotation Processing API para escribir el procesador de anotaciones que se ejecutará durante el proceso de compilación y recopilará todas las clases anotadas y creará el archivo de índice para su 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 soportados por 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 @IndexAnnotated meta-anotación. Esto creará en tiempo de compilación un archivo de índice: META-INF/annotations/com/test/YourCustomAnnotation que enumera todas las clases anotadas. Puede acccess el índice en tiempo de ejecución mediante la ejecución:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class) 
1

Google Reflections parece ser mucho más rápido que la primavera. Se encontró esta solicitud de función que hace referencia a 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 muy importante durante el desarrollo.Las reflexiones también parecen ser muy fáciles de usar para mi caso de uso (encuentre todos los implementadores de una interfaz).

+1

Si nos fijamos en el problema JIRA, hay comentarios allí que se alejaron de Reflections debido a problemas de estabilidad. –

28

simplemente solté un escáner súper rápido y ruta de clase ligera (git repo here) que no llama el cargador de clases para cargar las clases en la ruta de clase con el fin de determinar subclases, superclases, anotaciones, etc., sino que lee los encabezados de archivo de clase binarios directamente (inspirada por, pero más simple que, el escáner classpath de rmueller, vinculado en otro comentario).

Mi classpath scanner puede encontrar clases en la classpath que extienden una determinada superclase, que implementan una interfaz dada, o que tienen una anotación de clase determinada, y pueden encontrar archivos dentro de la classpath de cualquier tipo cuya ruta coincida con una expresión regular dada .

Aquí es un ejemplo de uso:

new FastClasspathScanner(new String[] 
     { "com.xyz.widget", "com.xyz.gizmo" }) // Whitelisted package prefixes 
    .matchSubclassesOf(DBModel.class, 
     // c is a subclass of DBModel 
     c -> System.out.println("Found subclass of DBModel: " + c.getName())) 
    .matchClassesImplementing(Runnable.class, 
     // c is a class that implements Runnable 
     c -> System.out.println("Found Runnable: " + c.getName())) 
    .matchClassesWithAnnotation(RestHandler.class, 
     // c is a class annotated with @RestHandler 
     c -> System.out.println("Found RestHandler annotation on class: " 
       + c.getName())) 
    .matchFilenamePattern("^template/.*\\.html", 
     // templatePath is a path on the classpath that matches the above pattern; 
     // inputStream is a stream opened on the file or zipfile entry. 
     // No need to close inputStream before exiting, it is closed by caller. 
     (templatePath, inputStream) -> { 
      try { 
       String template = IOUtils.toString(inputStream, "UTF-8"); 
       System.out.println("Found template: " + absolutePath 
         + " (size " + template.length() + ")"); 
      } catch (IOException e) { 
       throw new RuntimeException(e); 
      } 
     }) 
    .scan(); // Actually perform the scan 

El escáner también registra la última modificada por última vez marca de tiempo de cualquier archivo o directorio encontrado, y se puede ver si esa última fecha y hora modificada en último lugar se ha incrementado (lo que indica que algo en la ruta de clase se ha actualizado), llamando al:

boolean classpathContentsModified = 
    fastClassPathScanner.classpathContentsModifiedSinceScan(); 

Esto puede ser usado para permitir dinámica de clase-recarga si se actualiza algo en la ruta de clase, por ejemplo, para apoyar a reemplazar en caliente de las clases de controlador de ruta en una Servidor web. La llamada anterior es varias veces más rápida que la llamada original para escanear(), ya que solo se deben verificar las marcas de tiempo de modificación.

+1

¿Necesita esto Java 8 para ejecutarse? –

+0

Sí, requiere Java8. Utiliza transmisiones. Voy a ver si puedo modificarlo para usar solo v7. No puedo soportar asumir otras 700K de dependencias solo para obtener esto con 'scannotations' o incluso más con' reflexiones'. Suspiro. –

+1

Actualizado para usar Java7, no hay problema. Simplemente elimine las molestias y convierta las funciones para usar clases internas anónimas. Me gusta el estilo de 1 archivo. El código es bueno y está limpio, por lo que aunque no admite algunas cosas que me gustaría (clase + anotación al mismo tiempo), creo que sería muy fácil de agregar. ¡Buen trabajo! Si alguien no puede hacer el trabajo de modificar para v7, probablemente debería ir con 'Reflections'. Además, si está usando guava/etc y desea cambiar las colecciones, es fácil. Grandes comentarios dentro también. –

2

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

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

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

+2

Buena idea, pero si pones un valor 'nulo' como el segundo parámetro (que define la clase, en la que la jerarquía de herencia Spring buscará una clase que use la Anotación), siempre obtendrás un' nulo' de vuelta, según la implementación. – jonashackt

1

¿Es demasiado tarde para responder. yo diría, es mejor ir por las bibliotecas como ClassPathScanningCandidateComponentProvider o como Scannotations

Pero incluso después de que alguien quiere probar algunas manos en él con cargador de clases, he escrito algunos en mi propia para imprimir las anotaciones de 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 la configuración del archivo, se pone el nombre del paquete y Resolver referencia a una clase.

Cuestiones relacionadas