Analyse des annotations Java à l'exécution [fermé]

253

Quelle est la meilleure façon de rechercher le chemin de classe entier pour une classe annotée?

Je fais une bibliothèque et je veux permettre aux utilisateurs d'annoter leurs classes, donc lorsque l'application Web démarre, je dois analyser le chemin de classe entier pour une certaine annotation.

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

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

Alotor
la source

Réponses:

210

Utilisez org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Un fournisseur de composants qui analyse le chemin de classe à 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());
Arthur Ronald
la source
5
Merci pour l'information. Savez-vous également comment analyser le chemin de classe pour les classes dont les champs ont une annotation personnalisée?
Javatar
6
@Javatar Utilisez l'API de réflexion de Java. <YOUR_CLASS> .class.getFields () Pour chaque champ, appelez getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald
1
REMARQUE: si vous effectuez cette opération dans une application Spring, Spring évaluera et agira toujours en fonction des @Conditionalannotations. Donc, si une classe a sa @Conditionalvaleur renvoyant false, elle ne sera pas renvoyée par findCandidateComponents, même si elle correspondait au filtre du scanner. Cela m'a jeté aujourd'hui - j'ai fini par utiliser la solution de Jonathan ci-dessous à la place.
Adam Burley
1
@ArthurRonald Désolé, Arthur. Je veux dire que l' BeanDefinitionobjet ne fournit pas un moyen d'obtenir directement la classe. La chose la plus proche semble être celle getBeanClassNamequi renvoie un nom de classe complet, mais le comportement exact de cette méthode n'est pas clair. De plus, il n'est pas clair dans quel chargeur de classe la classe a été trouvée.
Max
3
@Max Essayez ceci: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins
149

Et une autre solution est les 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.
  • L'utilisation directe de Java Assist est également maladroite.
  • L'annovention est super légère et pratique. Pas encore d'intégration maven.
  • Les réflexions de Google font partie des collections Google Indexe tout et est super rapide.
Jonathan
la source
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). c'est tout.
zapp
4
dois-je spécifier un nom de package? caractère générique? quoi pour toutes les classes dans classpath?
Sunnyday
1
Attention, il n'y a toujours pas de progrès sur la prise en charge de Java 9: github.com/ronmamo/reflections/issues/186
Vadzim
la bibliothèque org.reflections ne fonctionne pas sous java 13 (peut-être plus tôt aussi). La première fois qu'on l'appelle, ça semble aller. les instanciations et utilisations suivantes échouent en disant que les URL de recherche ne sont pas configurées.
Evvo
44

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 les classes qui implémentent une interface donnée. (Avertissement, je suis l'auteur de ClassGraph.) ClassGraph peut construire une représentation abstraite du graphique de classe entier (toutes les classes, annotations, méthodes, paramètres de méthode et champs) en mémoire, pour toutes les classes sur le chemin de classe, ou pour les classes dans des packages sur liste blanche, et vous pouvez interroger ce graphique de classe comme vous le souhaitez. ClassGraph prend en charge plus de mécanismes de spécification de chemin de classe et de chargeurs de classe que tout autre scanner, et fonctionne également de manière transparente avec le nouveau système de module JPMS, donc si vous basez votre code sur ClassGraph, votre code sera au maximum portable. Voir l'API ici.

Luke Hutchison
la source
1
Est-ce que cela nécessite Java 8 pour fonctionner?
David George
1
Mis à jour pour utiliser Java7, pas de problème. Supprimez simplement les annotations et convertissez les fonctions pour utiliser des classes internes anonymes. J'aime le style 1 fichier. Le code est agréable et propre, donc même s'il ne prend pas en charge certaines choses que j'aimerais (classe + annotation en même temps), je pense que ce serait sacrément facile à ajouter. Bon travail! Si quelqu'un ne parvient pas à faire le travail de modification pour la v7, il devrait probablement y aller Reflections. De plus, si vous utilisez goyave / etc et que vous souhaitez changer les collections, c'est facile comme bonjour. Grands commentaires à l'intérieur aussi.
Andrew Backer du
2
@Alexandros merci, vous devriez vérifier ClassGraph, il est considérablement amélioré par rapport à FastClasspathScanner.
Luke Hutchison
2
@AndrewBacker ClassGraph (la nouvelle version de FastClasspathScanner) prend entièrement en charge les opérations booléennes, via des filtres ou des opérations de définition. Voir des exemples de code ici: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison
1
@Luke Hutchison Utilise déjà ClassGraph. M'a aidé à migrer vers Java 10. Bibliothèque vraiment utile.
Alexandros
25

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

Avertissement: je suis l'auteur.

rmuller
la source
20

Vous pouvez utiliser l'API Java Pluggable Annotation Processing pour écrire un processeur d'annotations qui sera exécuté pendant le processus de compilation et 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 possible de faire la découverte de classe annotée car vous n'avez pas besoin d'analyser votre chemin de classe au moment de l'exécution, ce qui est généralement une opération très lente. Cette approche fonctionne également avec n'importe quel chargeur de classe et pas seulement avec les 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 la méta-annotation @IndexAnnotated . Cela créera au moment de la compilation un fichier d'index: META-INF / annotations / com / test / YourCustomAnnotation répertoriant toutes les classes annotées. Vous pouvez accéder à l'index lors de l'exécution en exécutant:

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

Est-il trop tard pour répondre. Je dirais qu'il vaut mieux utiliser des bibliothèques comme ClassPathScanningCandidateComponentProvider ou des scannotations

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

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 config File, vous mettez le nom du paquet et le démasquez dans une classe.

kenobiwan
la source
3

Avec Spring, vous pouvez également simplement écrire ce qui suit en utilisant la classe AnnotationUtils. c'est à dire:

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

Pour plus de détails et toutes les différentes méthodes, consultez les documents officiels: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

magicien
la source
4
Bonne idée, mais si vous mettez une nullvaleur comme deuxième paramètre (qui définit la classe, dans laquelle la hiérarchie d'héritage Spring va rechercher une classe qui utilise l'annotation), vous obtiendrez toujours un nullretour, selon l'implémentation.
jonashackt
3

Il y a un merveilleux commentaire de zapp qui coule dans toutes ces réponses:

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

L'API Classloader n'a pas de méthode "énumérer", 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 jamais nécessaire (le rt.jar seul est de 48 Mo de nos jours!).

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

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

mfx
la source
2

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

Spring ClassPathScanningCandidateComponentProviderne découvre pas les interfaces.

madhu_karnati
la source
1

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

C'est une raison pour utiliser Reflections car le temps de démarrage de mon application est vraiment important pendant le développement. Les réflexions semblent également très faciles à utiliser pour mon cas d'utilisation (trouver tous les implémenteurs d'une interface).

Martin Aubele
la source
1
Si vous regardez le problème JIRA, il y a des commentaires selon lesquels ils se sont éloignés de Reflections en raison de problèmes de stabilité.
Wim Deblauwe
1

Si vous cherchez une alternative aux réflexions, je voudrais recommander Panda Utilities - AnnotationsScanner . C'est un scanner sans Guava (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 de classe actuel, les AnnotationsScannerProcesscaches sont tous récupérés ClassFiles, donc c'est très rapide.

Exemple simple d' AnnotationsScannerutilisation:

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
la source
1

Le printemps a quelque chose appelé une AnnotatedTypeScannerclasse.
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. Voici 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;
    }
swayamraina
la source