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.
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());
Et une autre solution est réflexions de Google .
Examen rapide:
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.
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.
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)
Essayez Scannotation .
Il peut être utilisé pour rechercher dans le classpath ou le répertoire lib de votre application Web des annotations spécifiques.
Vous voudrez peut-être utiliser http://code.google.com/p/annovention/
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].
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
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.
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.
Je ne sais pas si cela vous aidera ou non, mais vous pouvez vous pencher sur le projet Apache commons-discovery.
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);
Google Reflection si vous souhaitez également découvrir des interfaces.
Spring ClassPathScanningCandidateComponentProvider
ne découvre pas les interfaces.
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).
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;
}
Si vous voulez une bibliothèque Scala, utilisez Sclasner: https://github.com/ngocdaothanh/sclasner