Java-Annotationen zur Laufzeit scannen [geschlossen]

253

Wie kann der gesamte Klassenpfad am besten nach einer mit Anmerkungen versehenen Klasse durchsucht werden?

Ich mache eine Bibliothek und möchte den Benutzern erlauben, ihre Klassen mit Anmerkungen zu versehen. Wenn die Webanwendung gestartet wird, muss ich den gesamten Klassenpfad nach bestimmten Anmerkungen durchsuchen.

Kennen Sie eine Bibliothek oder eine Java-Einrichtung, um dies zu tun?

Bearbeiten: Ich denke über so etwas wie die neue Funktionalität für Java EE 5 Web Services oder EJBs nach. Sie kommentieren Ihre Klasse mit @WebServiceoder @EJBund das System findet diese Klassen beim Laden, sodass auf sie remote zugegriffen werden kann.

Alotor
quelle

Antworten:

210

Verwenden Sie org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Ein Komponentenanbieter, der den Klassenpfad von einem Basispaket aus scannt. Anschließend werden Ausschluss- und Einschlussfilter auf die resultierenden Klassen angewendet, um Kandidaten zu finden.

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
quelle
5
Danke für die Information. Wissen Sie auch, wie Sie Klassenpfade nach Klassen durchsuchen, deren Felder benutzerdefinierte Anmerkungen haben?
Javatar
6
@Javatar Verwenden Sie die Reflection-API von Java. <YOUR_CLASS> .class.getFields () Rufen Sie für jedes Feld getAnnotation (<YOUR_ANNOTATION>) auf
Arthur Ronald
1
HINWEIS: Wenn Sie dies in einer Spring-Anwendung tun, wird Spring weiterhin anhand von @ConditionalAnmerkungen auswerten und handeln . Wenn der @ConditionalWert einer Klasse also false zurückgibt, wird er nicht zurückgegeben findCandidateComponents, selbst wenn er mit dem Filter des Scanners übereinstimmt. Das hat mich heute umgehauen - ich habe stattdessen Jonathans Lösung unten verwendet.
Adam Burley
1
@ ArthurRonald Entschuldigung, Arthur. Ich meine, dass das BeanDefinitionObjekt keine Möglichkeit bietet, die Klasse direkt abzurufen. Das Nächste scheint zu sein, getBeanClassNamedas einen vollständig qualifizierten Klassennamen zurückgibt, aber das genaue Verhalten dieser Methode ist nicht klar. Es ist auch nicht klar, in welchem ​​Klassenlader die Klasse gefunden wurde.
Max
3
@ Max Versuchen Sie dies: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins
149

Eine weitere Lösung sind Google-Überlegungen .

Kurzübersicht:

  • Die Spring-Lösung ist der richtige Weg, wenn Sie Spring verwenden. Ansonsten ist es eine große Abhängigkeit.
  • Die direkte Verwendung von ASM ist etwas umständlich.
  • Die direkte Verwendung von Java Assist ist ebenfalls umständlich.
  • Annovention ist superleicht und praktisch. Noch keine Maven-Integration.
  • Google-Reflexionen ziehen in Google-Sammlungen. Indiziert alles und ist dann super schnell.
Jonathan
quelle
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). c'est tout.
Zapp
4
Muss ich einen Paketnamen angeben? Platzhalter? Was ist für alle Klassen im Klassenpfad?
Sonnentag
1
Beachten
Die Bibliothek org.reflections funktioniert nicht direkt unter Java 13 (möglicherweise auch früher). Das erste Mal, wenn es aufgerufen wird, scheint es in Ordnung zu sein. Nachfolgende Instanziierungen und Verwendungen schlagen fehl, da die Such-URLs nicht konfiguriert sind.
Evvo
44

Mit ClassGraph können Sie Klassen mit einer bestimmten Anmerkung finden und nach anderen interessanten Kriterien suchen, z. B. nach Klassen, die eine bestimmte Schnittstelle implementieren. (Haftungsausschluss, ich bin der Autor von ClassGraph.) ClassGraph kann eine abstrakte Darstellung des gesamten Klassendiagramms (alle Klassen, Anmerkungen, Methoden, Methodenparameter und Felder) im Speicher, für alle Klassen im Klassenpfad oder für Klassen in erstellen Whitelist-Pakete, und Sie können dieses Klassendiagramm abfragen, wie Sie möchten. ClassGraph unterstützt mehr Klassenpfadspezifikationsmechanismen und Klassenladeprogramme als jeder andere Scanner und funktioniert auch nahtlos mit dem neuen JPMS-Modulsystem. Wenn Sie Ihren Code also auf ClassGraph basieren, ist Ihr Code maximal portabel. Siehe die API hier.

Luke Hutchison
quelle
1
Benötigt dies Java 8 zum Ausführen?
David George
1
Aktualisiert auf Java7, kein Problem. Entfernen Sie einfach die Anmerkungen und konvertieren Sie die Funktionen in anonyme innere Klassen. Ich mag den 1-Dateistil. Der Code ist schön und sauber, und obwohl er einige Dinge, die ich gerne hätte (Klasse + Anmerkung gleichzeitig), nicht unterstützt, denke ich, dass das verdammt einfach hinzuzufügen wäre. Gute Arbeit! Wenn es jemandem nicht gelingt, die Arbeit für v7 zu ändern, sollte er wahrscheinlich mitmachenReflections . Auch wenn Sie Guave / etc verwenden und die Sammlungen ändern möchten, ganz einfach. Tolle Kommentare auch drinnen.
Andrew Backer
2
@Alexandros danke, du solltest dir ClassGraph ansehen, es ist deutlich besser als FastClasspathScanner.
Luke Hutchison
2
@AndrewBacker ClassGraph (die neue Version von FastClasspathScanner) unterstützt Boolesche Operationen über Filter oder Set-Operationen vollständig. Siehe Codebeispiele hier: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison
1
@ Luke Hutchison verwendet bereits ClassGraph. Hat mir bei der Migration auf Java 10 geholfen. Wirklich nützliche Bibliothek.
Alexandros
25

Wenn Sie ein wirklich geringes Gewicht (keine Abhängigkeiten, einfache API, 15-KB-JAR-Datei) und eine sehr schnelle Lösung wünschen , besuchen annotation-detectorSie https://github.com/rmuller/infomas-asl

Haftungsausschluss: Ich bin der Autor.

Müller
quelle
20

Sie können die Java Pluggable Annotation Processing API verwenden, um einen Annotationsprozessor zu schreiben, der während des Kompilierungsprozesses ausgeführt wird, alle mit Anmerkungen versehenen Klassen sammelt und die Indexdatei für die Laufzeit erstellt.

Dies ist der schnellste Weg, um annotierte Klassen zu erkennen, da Sie Ihren Klassenpfad zur Laufzeit nicht scannen müssen, was normalerweise sehr langsam ist. Dieser Ansatz funktioniert auch mit jedem Klassenladeprogramm und nicht nur mit URLClassLoadern, die normalerweise von Laufzeitscannern unterstützt werden.

Der obige Mechanismus ist bereits in ClassIndex implementiert Bibliothek .

Um es zu verwenden, kommentieren Sie Ihre benutzerdefinierte Annotation mit @IndexAnnotated Meta-Annotation. Dadurch wird zur Kompilierungszeit eine Indexdatei erstellt: META-INF / annotations / com / test / YourCustomAnnotation, in der alle mit Anmerkungen versehenen Klassen aufgelistet sind. Sie können zur Laufzeit auf den Index zugreifen, indem Sie Folgendes ausführen:

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

Ist es zu spät zu antworten? Ich würde sagen, es ist besser, Bibliotheken wie ClassPathScanningCandidateComponentProvider oder ähnliches zu verwenden Scannotations zu verwenden

Aber selbst nachdem jemand es mit classLoader ausprobieren möchte, habe ich einige selbst geschrieben, um die Anmerkungen von Klassen in einem Paket zu drucken:

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;

}
}

In der Konfigurationsdatei geben Sie den Paketnamen ein und entfernen die Zuordnung zu einer Klasse.

Kenobiwan
quelle
3

Mit Spring können Sie auch einfach Folgendes mit der AnnotationUtils-Klasse schreiben. dh:

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

Weitere Informationen und alle verschiedenen Methoden finden Sie in den offiziellen Dokumenten: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

Zauberkünstler
quelle
4
Gute Idee, aber wenn Sie einen nullWert als zweiten Parameter eingeben (der die Klasse definiert, in der die Vererbungshierarchie Spring nach einer Klasse sucht, die die Annotation verwendet), erhalten Sie nullje nach Implementierung immer eine Rückmeldung .
Jonashackt
3

Es gibt einen wunderbaren Kommentar von zapp , der in all diesen Antworten versinkt:

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

Die Classloader-API verfügt nicht über eine "Enumerate" -Methode, da das Laden von Klassen eine "On-Demand" -Aktivität ist. In Ihrem Klassenpfad befinden sich normalerweise Tausende von Klassen, von denen nur ein Bruchteil jemals benötigt wird (rt.jar) allein ist heutzutage 48MB!).

Also, auch wenn Sie könnten alle Klassen aufzählen , wäre dies sehr zeit- und speicherintensiv.

Der einfache Ansatz besteht darin, die betroffenen Klassen in einer Setup-Datei aufzulisten (XML oder was auch immer Ihren Vorstellungen entspricht). Wenn Sie dies automatisch tun möchten, beschränken Sie sich auf ein JAR- oder ein Klassenverzeichnis.

mfx
quelle
2

Google Reflection wenn Sie auch Schnittstellen entdecken möchten.

Der Frühling ClassPathScanningCandidateComponentProviderentdeckt keine Schnittstellen.

madhu_karnati
quelle
1

Google Reflections scheint viel schneller zu sein als der Frühling. Es wurde diese Funktionsanforderung gefunden, die diesen Unterschied behebt: http://www.opensaga.org/jira/browse/OS-738

Dies ist ein Grund, Reflections zu verwenden, da die Startzeit meiner Anwendung während der Entwicklung sehr wichtig ist. Reflexionen scheinen auch für meinen Anwendungsfall sehr einfach zu verwenden zu sein (alle Implementierer einer Schnittstelle finden).

Martin Aubele
quelle
1
Wenn Sie sich das JIRA-Problem ansehen, gibt es dort Kommentare, dass sie sich aufgrund von Stabilitätsproblemen von Reflections entfernt haben.
Wim Deblauwe
1

Wenn Sie nach einer Alternative zu Reflexionen suchen, würde ich Panda Utilities - AnnotationsScanner empfehlen . Es ist ein Guaven-freier Scanner (Guava hat ~ 3 MB, Panda Utilities hat ~ 200 KB), der auf dem Quellcode der Reflexionsbibliothek basiert.

Es ist auch für zukünftige Suchvorgänge vorgesehen. Wenn Sie mehrere eingeschlossene Quellen scannen oder sogar eine API bereitstellen möchten, mit der jemand den aktuellen Klassenpfad scannen kann, werden AnnotationsScannerProcessalle abgerufenen Caches zwischengespeichert ClassFiles, sodass dies sehr schnell geht.

Einfaches AnnotationsScannerAnwendungsbeispiel:

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
quelle
1

Der Frühling hat eine AnnotatedTypeScannerKlasse.
Diese Klasse wird intern verwendet

ClassPathScanningCandidateComponentProvider

Diese Klasse enthält den Code für das eigentliche Scannen des Klassenpfads . Dazu werden die zur Laufzeit verfügbaren Klassenmetadaten verwendet.

Man kann diese Klasse einfach erweitern oder dieselbe Klasse zum Scannen verwenden. Unten ist die Konstruktordefinition.

/**
     * 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
quelle