Question

Quel est le meilleur moyen de rechercher l'intégralité d'une classe annotée dans l'ensemble du chemin de classes?

Je fais une bibliothèque et je veux permettre aux utilisateurs d'annoter leurs classes. Par conséquent, lorsque l'application Web démarre, je dois analyser le chemin d'accès aux classes pour y trouver certaines annotations.

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

Modifier: je pense à quelque chose comme la nouvelle fonctionnalité des services Web Java EE 5 ou des EJB. Vous annotez votre classe avec @WebService ou @EJB et le système trouve ces classes lors du chargement pour les rendre accessibles à distance.

Était-ce utile?

La solution

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());

Autres conseils

Une autre solution est les réflexions Google .

Commentaire rapide:

  • La solution Spring est la voie à suivre si vous utilisez Spring. Sinon, c'est une grosse dépendance.
  • Utiliser ASM directement est un peu fastidieux.
  • L’utilisation directe de Java Assist est également maladroite.
  • Annovention est très léger et pratique. Pas encore d'intégration maven.
  • Les réflexions de Google attirent les collections de Google. Indexe tout et est super rapide.

Vous pouvez trouver des classes avec n'importe quelle annotation 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 davantage de mécanismes de spécification de chemin d'accès et de chargeur de classe que tout autre scanner, ainsi que fonctionne 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.

Si vous souhaitez une légèreté (aucune dépendance, une API simple, un fichier jar de 15 ko) et une solution très rapide , consultez annotation-detector disponible sur https://github.com/rmuller/infomas-asl

Avertissement: je suis l'auteur.

Vous pouvez utiliser l'API Java Pluggable Annotation Processing pour écrire un processeur d'annotation 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 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 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 méta-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)

Essayez Scannotation .

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

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 les différentes méthodes, consultez la documentation officielle: https: // docs. spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

Un peu hors de propos, mais Spring fait aussi quelque chose de similaire, en utilisant < contexte: 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].

Est-il trop tard pour répondre. Je dirais qu'il est préférable de passer par des bibliothèques telles que ClassPathScanningCandidateComponentProvider ou similaire, 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 ne le supprimez pas dans une classe.

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

Ainsi, 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.

Je ne sais pas si cela vous aidera ou non, mais vous pouvez vous pencher sur le projet de découverte apache commons.

projet de découverte

Google Les réflexions semblent être beaucoup plus rapides que le printemps. Trouvé cette demande de fonctionnalité qui adresse cette différence: http://www.opensaga.org/jira/ parcourir / 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. Reflections semble aussi être très facile à utiliser pour mon cas d'utilisation (trouver tous les développeurs d'une interface).

Si vous recherchez une alternative à réflexions , je vous recommande Utilitaires Panda - 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 extraits récupérés ClassFiles, ce qui en fait une solution extrêmement rapide.

Exemple simple d'utilisation 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);

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

Spring ClassPathScanningCandidateComponentProvider ne découvre pas les interfaces.

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

ClassPathScanningCandidateComponentProvider

Cette classe contient le code permettant d'analyser les ressources classpath . Pour ce faire, il utilise les métadonnées de classe disponibles à l'exécution.

On peut simplement étendre cette classe ou utiliser la même classe pour le balayage. 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;
    }

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top