web-dev-qa-db-fra.com

Analyse des annotations Java lors de l'exécution

Quel est le meilleur moyen de rechercher dans tout le classpath une classe annotée?

Je fais une bibliothèque et je veux autoriser les utilisateurs à annoter leurs classes. Ainsi, lorsque l'application Web démarre, je dois analyser le chemin d'accès aux classes complet à la recherche de certaines annotations.

Connaissez-vous une bibliothèque ou une installation Java pour le faire?

Edit: Je pense à quelque chose comme la nouvelle fonctionnalité de Java EE 5 Web Services ou EJB. Vous annotez votre classe avec @WebService ou @EJB et le système trouve ces classes lors du chargement afin qu'elles soient accessibles à distance.

236
Alotor

Utilisez org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Un fournisseur de composants qui analyse le chemin d'accès aux classes à partir d'un package de base. Il applique ensuite des filtres d'exclusion et d'inclusion aux classes résultantes pour trouver des candidats.

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());
202
Arthur Ronald

Et une autre solution est réflexions de Google .

Examen rapide:

  • La solution Spring est la voie à suivre si vous utilisez Spring. Sinon, c'est une grosse dépendance.
  • L'utilisation directe d'ASM est un peu lourde.
  • Utiliser JavaAssist directement est également maladroit.
  • Annovention est super léger et pratique. Pas d'intégration maven pour le moment.
  • Les réflexions de Google attirent les collections de Google. Indexe tout et ensuite, c'est super rapide.
142
Jonathan

Vous pouvez trouver des classes avec n'importe quelle annotation donnée avec ClassGraph , ainsi que rechercher d'autres critères d'intérêt, par exemple. classes qui implémentent une interface donnée. (Disclaimer, je suis l'auteur de ClassGraph.) ClassGraph peut créer une représentation abstraite du graphe de classe entier (toutes les classes, annotations, méthodes, paramètres de méthode et champs) en mémoire, pour toutes les classes du chemin de classe ou pour les classes de liste blanche, et vous pouvez interroger ce graphe de classe comme vous le souhaitez. ClassGraph prend en charge plus de mécanismes de spécification de chemin d'accès et de chargeur de classe que n'importe quel autre scanner et fonctionne également de manière transparente avec le nouveau système de module JPMS. Ainsi, si vous basez votre code sur ClassGraph, votre code sera extrêmement portable. Voir l'API ici.

41
Luke Hutchison

Si vous voulez un poids très léger (pas de dépendances, une API simple, un fichier jar de 15 ko) et très rapidement solution, jetez un oeil à annotation-detector trouvé à https://github.com/rmuller/infomas-asl

Disclaimer: je suis l'auteur.

23
rmuller

Vous pouvez utiliser l'API de traitement d'annotations pluggable Java pour écrire le processeur d'annotations qui sera exécuté lors de la compilation, collectera toutes les classes annotées et construira le fichier d'index pour une utilisation à l'exécution.

C'est le moyen le plus rapide de faire une découverte de classe annotée car vous n'avez pas besoin d'analyser votre chemin d'accès aux classes au moment de l'exécution, ce qui est généralement une opération très lente. De plus, cette approche fonctionne avec n’importe quel chargeur de classe et pas seulement avec URLClassLoaders généralement pris en charge par les scanners d’exécution.

Le mécanisme ci-dessus est déjà implémenté dans la bibliothèque ClassIndex .

Pour l'utiliser, annotez votre annotation personnalisée avec @ IndexAnnotated meta-annotation. Cela créera au moment de la compilation un fichier d'index: META-INF/annotations/com/test/YourCustomAnnotation listant toutes les classes annotées. Vous pouvez accéder à l'index au moment de l'exécution en exécutant:

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

Essayez Scannotation .

Il peut être utilisé pour rechercher dans le classpath ou le répertoire lib de votre application Web des annotations spécifiques.

6
Wolf

Vous voudrez peut-être utiliser http://code.google.com/p/annovention/

5
Animesh

Légèrement décalé, mais Spring fait également quelque chose de similaire, en utilisant <context: composant-scan> , dont vous pourriez peut-être étudier le code source?

Spring permet de détecter automatiquement les classes "stéréotypées" [...]. Pour détecter automatiquement ces classes et enregistrer les beans correspondants, vous devez inclure le [contexte: composant-scan element].

3
toolkit

Avec Spring, vous pouvez également écrire ce qui suit à l’aide de la classe AnnotationUtils. c'est à dire.:

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

Pour plus de détails et pour connaître toutes les méthodes, consultez la documentation officielle: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

3
magiccrafter

Est-il trop tard pour répondre? Je dirais que c'est mieux d'aller par les bibliothèques comme ClassPathScanningCandidateComponentProvider ou comme Scannotations

Mais même après que quelqu'un veuille essayer quelque chose avec classLoader, j'en ai écrit moi-même pour imprimer les annotations des classes d'un paquet:

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;

}
}

Et dans le fichier de configuration, vous mettez le nom du paquet et le supprimez dans une classe.

3
yashpandey

L'API Classloader n'a pas de méthode "enumerate", car le chargement de classe est une activité "à la demande" - vous avez généralement des milliers de classes dans votre chemin de classe, dont seulement une fraction sera nécessaire (le fichier rt.jar seul fait 48Mo maintenant!).

Donc, même si vous pouviez énumérer toutes les classes, cela prendrait beaucoup de temps et de mémoire.

L’approche la plus simple consiste à répertorier les classes concernées dans un fichier d’installation (xml ou celui qui vous convient le mieux); si vous voulez le faire automatiquement, limitez-vous à un seul JAR ou à un répertoire de classe.

2
mfx

Je ne sais pas si cela vous aidera ou non, mais vous pouvez vous pencher sur le projet Apache commons-discovery.

projet de découverte

2
Ian McLaird

Si vous cherchez une alternative à réflexions , j'aimerais recommander Panda Utilities - AnnotationsScanner . C'est un scanner sans goyave (Guava a ~ 3 Mo, Panda Utilities a ~ 200 Ko) basé sur le code source de la bibliothèque de réflexions.

Il est également dédié aux recherches futures. Si vous souhaitez analyser plusieurs fois les sources incluses ou même fournir une API, qui permet à quelqu'un d'analyser le chemin d'accès aux classes actuel, AnnotationsScannerProcess met en cache tous les éléments récupérés ClassFiles, ce qui le rend très rapide.

Exemple simple d'utilisation de AnnotationsScanner:

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

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

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

Google Reflection si vous souhaitez également découvrir des interfaces.

Spring ClassPathScanningCandidateComponentProvider ne découvre pas les interfaces.

1
madhu_karnati

Google Reflections semble être beaucoup plus rapide que Spring. Trouvé cette demande de fonctionnalité qui adresse cette différence: http://www.opensaga.org/jira/browse/OS-738

C'est une raison d'utiliser Reflections car le temps de démarrage de mon application est vraiment important pendant le développement. Reflections semble aussi être très facile à utiliser pour mon cas d'utilisation (trouver tous les développeurs d'une interface).

1
Martin Aubele

Spring a quelque chose qui s'appelle une classe AnnotatedTypeScanner.
Cette classe utilise en interne

ClassPathScanningCandidateComponentProvider

Cette classe a le code pour l'analyse réelle des ressources classpath. Pour ce faire, il utilise les métadonnées de classe disponibles au moment de l'exécution.

On peut simplement étendre cette classe ou utiliser la même classe pour la numérisation. Vous trouverez ci-dessous la définition du constructeur.

/**
     * 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;
    }
1
swayamraina

Si vous voulez une bibliothèque Scala, utilisez Sclasner: https://github.com/ngocdaothanh/sclasner

0
Ngoc Dao