Scansione delle annotazioni Java in fase di esecuzione [chiuso]


253

Qual è il modo migliore di cercare in tutto il percorso di classe una classe annotata?

Sto facendo una libreria e voglio consentire agli utenti di annotare le loro classi, quindi quando si avvia l'applicazione Web ho bisogno di scansionare l'intero percorso di classe per una certa annotazione.

Conosci una libreria o una struttura Java per farlo?

Modifica: sto pensando a qualcosa come la nuova funzionalità per Java EE 5 Web Services o EJB. Annoti la tua classe con @WebServiceoe @EJBil sistema trova queste classi durante il caricamento in modo che siano accessibili da remoto.

Risposte:


210

Usa org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Un provider di componenti che analizza il percorso di classe da un pacchetto di base. Quindi applica exclude e include i filtri alle classi risultanti per trovare i candidati.

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

5
Grazie per l'informazione. Sai anche come scansionare il percorso di classe per le classi i cui campi hanno annotazioni personalizzate?
Javatar

6
@Javatar Usa l'API di riflessione di Java. <YOUR_CLASS> .class.getFields () Per ogni campo, invoca getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald

1
NOTA: se lo stai facendo all'interno di un'applicazione Spring, Spring valuterà e agirà comunque in base alle @Conditionalannotazioni. Pertanto, se una classe ha un @Conditionalvalore che restituisce false, non verrà restituita da findCandidateComponents, anche se corrisponde al filtro dello scanner. Questo mi ha gettato oggi - ho finito per usare invece la soluzione di Jonathan qui sotto.
Adam Burley,

1
@ArthurRonald Siamo spiacenti, Arthur. Voglio dire che l' BeanDefinitionoggetto non fornisce un modo per ottenere direttamente la classe. La cosa più vicina sembra essere quella getBeanClassNameche restituisce un nome di classe completo, ma il comportamento esatto di questo metodo non è chiaro. Inoltre, non è chiaro in quale caricatore di classe sia stata trovata la classe.
Max

3
@Max Prova questo: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins

149

E un'altra soluzione sono le riflessioni di Google .

Recensione veloce:

  • La soluzione Spring è la strada da percorrere se stai usando Spring. Altrimenti è una grande dipendenza.
  • L'uso diretto di ASM è un po 'complicato.
  • Anche l'uso diretto di Java Assist è ingombrante.
  • L'annuncio è super leggero e conveniente. Nessuna integrazione ancora.
  • Le riflessioni di Google attirano le raccolte di Google. Indicizza tutto e poi è super veloce.

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). tout tout.
zapp

4
devo specificare un nome per il pacchetto? carta jolly? cosa per tutte le classi in classpath?
Sunnyday,

1
Attenzione che non ha ancora progressi sul supporto Java 9: github.com/ronmamo/reflections/issues/186
Vadzim,

la libreria org.reflections non funziona proprio sotto Java 13 (forse anche prima). La prima volta che viene chiamato sembra essere ok. le istanze e gli usi successivi falliscono dicendo che gli URL di ricerca non sono configurati.
Evvo

44

Puoi trovare le classi con qualsiasi annotazione data con ClassGraph , nonché cercare altri criteri di interesse, ad esempio le classi che implementano una determinata interfaccia. (Dichiarazione di non responsabilità, sono l'autore di ClassGraph.) ClassGraph può creare una rappresentazione astratta dell'intero grafico di classe (tutte le classi, annotazioni, metodi, parametri del metodo e campi) in memoria, per tutte le classi sul percorso di classe o per le classi in pacchetti autorizzati e puoi interrogare quel grafico di classe come preferisci. ClassGraph supporta più meccanismi di specifica del classpath e classloader rispetto a qualsiasi altro scanner e funziona perfettamente anche con il nuovo sistema di moduli JPMS, quindi se si basa il codice su ClassGraph, il codice sarà al massimo portatile. Vedi l'API qui.


1
Questo ha bisogno di Java 8 per funzionare?
David George,

1
Aggiornato per usare Java7, nessun problema. Basta rimuovere i fastidi e convertire le funzioni per utilizzare classi interne anonime. Mi piace lo stile di 1 file. Il codice è bello e pulito, quindi anche se non supporta alcune cose che vorrei (classe + annotazione allo stesso tempo) penso che sarebbe dannatamente facile da aggiungere. Ottimo lavoro! Se qualcuno non riesce a fare il lavoro di modifica per v7, probabilmente dovrebbe andare avanti Reflections. Inoltre, se stai usando guava / etc e vuoi cambiare le raccolte, facile come una torta. Ottimi commenti anche all'interno.
Andrew Backer,

2
@Alexandros grazie, dovresti dare un'occhiata a ClassGraph, è notevolmente migliorato su FastClasspathScanner.
Luke Hutchison,

2
@AndrewBacker ClassGraph (la nuova versione di FastClasspathScanner) ha il pieno supporto per le operazioni booleane, tramite filtri o operazioni impostate. Vedi esempi di codice qui: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison

1
@Luke Hutchison Già utilizzando ClassGraph. Mi ha aiutato con la migrazione a Java 10. Libreria davvero utile.
Alexandros,


20

È possibile utilizzare l'API Java Pluggable Elaboration Annotation per scrivere un processore di annotazioni che verrà eseguito durante il processo di compilazione e raccoglierà tutte le classi annotate e creerà il file indice per l'uso in fase di runtime.

Questo è il modo più veloce possibile per eseguire il rilevamento di classi con annotazioni perché non è necessario eseguire la scansione del percorso di classe in fase di esecuzione, che di solito è un'operazione molto lenta. Anche questo approccio funziona con qualsiasi classloader e non solo con URLClassLoaders generalmente supportati dagli scanner di runtime.

Il meccanismo sopra è già implementato nella libreria ClassIndex .

Per usarlo annota la tua annotazione personalizzata con @IndexAnnotation meta-annotazione. Questo creerà in fase di compilazione un file indice: META-INF / annotations / com / test / YourCustomAnnotation che elenca tutte le classi annotate. È possibile accedere all'indice in fase di esecuzione eseguendo:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)

5

È troppo tardi per rispondere. Direi che è meglio andare da biblioteche come ClassPathScanningCandidateComponentProvider o come Scannotations

Ma anche dopo che qualcuno vuole provare alcune mani con classLoader, ne ho scritte alcune per conto mio per stampare le annotazioni dalle classi in un pacchetto:

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;

}
}

E in Config File, metti il ​​nome del pacchetto e lo scomponi in una classe.


3

Con Spring puoi anche semplicemente scrivere quanto segue usando la classe AnnotationUtils. vale a dire:

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

Per maggiori dettagli e tutti i diversi metodi consultare i documenti ufficiali: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html


4
Bella idea, ma se si inserisce un nullvalore come secondo parametro (che definisce la classe, in cui la gerarchia dell'ereditarietà Spring eseguirà la scansione di una classe che utilizza l'Annotazione), si otterrà comunque un nullritorno, in base all'implementazione.
jonashackt,

3

C'è un meraviglioso commento di zapp che affonda in tutte quelle risposte:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)

2

L'API Classloader non ha un metodo "enumerato", perché il caricamento delle classi è un'attività "su richiesta" - di solito ci sono migliaia di classi nel tuo percorso di classe, solo una parte delle quali sarà mai necessaria (il rt.jar al giorno d'oggi è solo 48 MB!).

Quindi, anche se potessi enumerare tutte le classi, ciò richiederebbe molto tempo e memoria.

L'approccio semplice è quello di elencare le classi interessate in un file di installazione (xml o qualunque cosa tu voglia); se vuoi farlo automaticamente, limitati a un JAR o una directory di classe.



1

Google Reflections sembra essere molto più veloce di Spring. Ho trovato questa richiesta di funzionalità che risolve questa differenza: http://www.opensaga.org/jira/browse/OS-738

Questo è un motivo per usare Reflections poiché il tempo di avvio della mia applicazione è molto importante durante lo sviluppo. Reflections sembra anche essere molto facile da usare per il mio caso d'uso (trova tutti gli implementatori di un'interfaccia).


1
Se guardi al problema JIRA, ci sono commenti che si sono allontanati da Reflections a causa di problemi di stabilità.
Wim Deblauwe,

1

Se stai cercando un'alternativa alle riflessioni, vorrei raccomandare Panda Utilities - AnnotationsScanner . È uno scanner privo di Guava (Guava ha ~ 3 MB, Panda Utilities ha ~ 200 kb) basato sul codice sorgente della libreria riflessioni.

È anche dedicato a ricerche future. Se desideri scansionare più volte fonti incluse o anche fornire un'API, che consente a qualcuno di scansionare il percorso di classe corrente, AnnotationsScannerProcesstutte le cache vengono recuperate ClassFiles, quindi è davvero veloce.

Semplice esempio di AnnotationsScannerutilizzo:

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

La primavera ha qualcosa chiamato una AnnotatedTypeScannerclasse.
Questa classe utilizza internamente

ClassPathScanningCandidateComponentProvider

Questa classe ha il codice per la scansione effettiva delle risorse del percorso di classe . Lo fa usando i metadati di classe disponibili in fase di esecuzione.

Si può semplicemente estendere questa classe o utilizzare la stessa classe per la scansione. Di seguito è la definizione del costruttore.

/**
     * 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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.