Come leggo tutte le classi da un pacchetto Java nel classpath?


94

Ho bisogno di leggere le classi contenute in un pacchetto Java. Queste classi sono in classpath. Devo eseguire questa operazione direttamente da un programma Java. Conosci un modo semplice per farlo?

List<Class> classes = readClassesFrom("my.package")

7
In parole povere, no, non puoi farlo facilmente. Ci sono alcuni trucchi estremamente lunghi che funzionano in alcune situazioni, ma suggerisco caldamente un design diverso.
skaffman


La soluzione potrebbe essere trovata nel progetto Weld .
Ondra Žižka

Fare riferimento a questo link per la risposta: stackoverflow.com/questions/176527/…
Karthik E

Risposte:


48

Se hai Spring nel tuo classpath, allora lo farà quanto segue.

Trova tutte le classi in un pacchetto annotate con XmlRootElement:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}

Grazie per lo snippet di codice. :) Se vuoi evitare la duplicazione della Classe in un elenco di candidati, cambia il tipo di raccolta da Elenco a Set. E usa hasEnclosingClass () e getEnclosingClassName () di classMetaData. Usa il metodo getEnclosingClass su una classe sottostante
traeper

29

Potresti usare il progetto Reflections descritto qui

È abbastanza completo e facile da usare.

Breve descrizione dal sito sopra:

Reflections esegue la scansione del percorso di classe, indicizza i metadati, consente di interrogarlo in fase di esecuzione e può salvare e raccogliere tali informazioni per molti moduli all'interno del progetto.

Esempio:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);

Funziona in Equinox? E se è così, può farlo senza attivare i plugin?
Tag

funziona per l'equinozio. nelle versioni precedenti di Reflections, aggiungi bundle jar vfsType. vedi qui
zapp

28

Io uso questo, funziona con file o archivi jar

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}

1
funziona se si elimina il riferimento a File.Separator e si usa semplicemente "/"
Will Glass

1
È necessaria un'ulteriore modifica affinché funzioni con i file jar quando il percorso contiene spazi. Devi decodificare il percorso del file jar. Modifica: jarFileName = packageURL.getFile (); a: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass

ottengo solo il nome del pacchetto in jar .. non ottengo il nome della classe
AutoMEta

new File (uri) risolverà il tuo problema con gli spazi.
Trejkaz

entryName.lastIndexOf ('.') sarebbe -1
marstone

11

Spring ha implementato un'eccellente funzione di ricerca del percorso di classe in PathMatchingResourcePatternResolver . Se usi il classpath*prefisso:, puoi trovare tutte le risorse, comprese le classi in una data gerarchia, e persino filtrarle se vuoi. Quindi è possibile utilizzare i figli di AbstractTypeHierarchyTraversingFilter, AnnotationTypeFiltere AssignableTypeFilterper filtrare tali risorse sia a livello di classe annotazioni o su interfacce implementano.


6

Java 1.6.0_24:

public static File[] getPackageContent(String packageName) throws IOException{
    ArrayList<File> list = new ArrayList<File>();
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                            .getResources(packageName);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        File dir = new File(url.getFile());
        for (File f : dir.listFiles()) {
            list.add(f);
        }
    }
    return list.toArray(new File[]{});
}

Questa soluzione è stata testata all'interno dell'ambiente EJB .


6

Scannotation and Reflections utilizza l'approccio della scansione del percorso di classe:

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

Un altro approccio consiste nell'utilizzare l' API Java Pluggable Annotation Processing per scrivere un processore di annotazioni che raccoglierà tutte le classi annotate in fase di compilazione e creerà il file di indice per l'utilizzo in runtime. Questo meccanismo è implementato nella libreria ClassIndex :

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

3

Quella funzionalità è ancora sospettosamente mancante dall'API di riflessione Java, per quanto ne so. Puoi ottenere un oggetto pacchetto semplicemente facendo questo:

Package packageObj = Package.getPackage("my.package");

Ma come probabilmente avrai notato, questo non ti permetterà di elencare le classi in quel pacchetto. A partire da ora, devi adottare una sorta di approccio più orientato al filesystem.

Ho trovato alcune implementazioni di esempio in questo post

Non sono sicuro al 100% che questi metodi funzioneranno quando le tue classi saranno sepolte in file JAR, ma spero che uno di questi lo faccia per te.

Sono d'accordo con @skaffman ... se hai un altro modo per farlo, ti consiglio di farlo invece.


4
Non è sospetto, semplicemente non funziona in questo modo. Le classi non "appartengono" ai pacchetti, hanno riferimenti ad essi. L'associazione non punta nell'altra direzione.
skaffman

1
@skaffman Punto molto interessante. Non ci ho mai pensato in quel modo. Quindi fintanto che stiamo vagando lungo quella scia di pensiero, perché l'associazione non è bidirezionale (questo è più per la mia curiosità ora)?
Codice di scrittura Brent,

3

Il meccanismo più robusto per elencare tutte le classi in un dato pacchetto è attualmente ClassGraph , perché gestisce la più ampia gamma possibile di meccanismi di specificazione del percorso di classe , incluso il nuovo sistema di moduli JPMS. (Sono l'autore.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}

L'ho scoperto solo un paio di anni dopo. Questo è un grande strumento! Funziona molto più velocemente della riflessione e mi piace che non invoca inizializzatori statici. Proprio quello di cui avevo bisogno per risolvere un problema che abbiamo avuto.
akagixxer

2

eXtcos sembra promettente. Immagina di voler trovare tutte le classi che:

  1. Estendi dalla classe "Component" e memorizzali
  2. Sono annotati con "MyComponent" e
  3. Sono nel pacchetto "comune".

Con eXtcos questo è semplice come

ClasspathScanner scanner = new ClasspathScanner();
final Set<Class> classStore = new ArraySet<Class>();

Set<Class> classes = scanner.getClasses(new ClassQuery() {
    protected void query() {
        select().
        from(“common”).
        andStore(thoseExtending(Component.class).into(classStore)).
        returning(allAnnotatedWith(MyComponent.class));
    }
});

2
  1. Bill Burke ha scritto un ( bell'articolo sulla scansione di classe] e poi ha scritto Scannotation .

  2. Hibernate ha già scritto questo:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI potrebbe risolvere questo problema, ma non lo so - non ho ancora studiato a fondo

.

@Inject Instance< MyClass> x;
...
x.iterator() 

Anche per le annotazioni:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}

Il link all'articolo è morto.
user2418306

1

Mi è capitato di averlo implementato e funziona nella maggior parte dei casi. Dato che è lungo, lo metto in un file qui .

L'idea è di trovare la posizione del file di origine della classe che è disponibile nella maggior parte dei casi (un'eccezione nota sono i file di classe JVM, per quanto ho testato). Se il codice si trova in una directory, esamina tutti i file e individua solo i file di classe. Se il codice si trova in un file JAR, scansiona tutte le voci.

Questo metodo può essere utilizzato solo quando:

  1. Hai una classe che si trova nello stesso pacchetto che vuoi scoprire, questa classe è chiamata SeedClass. Ad esempio, se desideri elencare tutte le classi in "java.io", la classe seed potrebbe essere java.io.File.

  2. Le tue classi si trovano in una directory o in un file JAR che contiene informazioni sul file sorgente (non il file del codice sorgente, ma solo il file sorgente). Per quanto ho provato, funziona quasi al 100% tranne la classe JVM (quelle classi vengono con la JVM).

  3. Il programma deve disporre dell'autorizzazione per accedere a ProtectionDomain di tali classi. Se il tuo programma è caricato localmente, non dovrebbero esserci problemi.

Ho testato il programma solo per il mio uso regolare, quindi potrebbe ancora avere problemi.

Spero che aiuti.


1
sembra essere una roba molto interessante! cercherò di usarlo. Se ho trovato utile per me, posso utilizzare il tuo codice in un progetto open source?

1
Mi sto preparando anche all'open-source. Quindi vai avanti: D
NawaMan

@NawaMan: Lo hai aperto, finalmente? In caso affermativo: dove possiamo trovare l'ultima versione? Grazie!
Joanis

1

Ecco un'altra opzione, leggera modifica a un'altra risposta sopra / sotto:

Reflections reflections = new Reflections("com.example.project.package", 
    new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = 
    reflections.getSubTypesOf(Object.class);

0

Ai tempi in cui le applet erano un luogo comune, si poteva avere un URL sul classpath. Quando il programma di caricamento classi richiedeva una classe, cercava in tutte le posizioni sul percorso classi, comprese le risorse http. Poiché puoi avere cose come URL e directory sul classpath, non esiste un modo semplice per ottenere un elenco definitivo delle classi.

Tuttavia, puoi avvicinarti abbastanza. Alcune delle biblioteche di Spring lo stanno facendo adesso. Puoi ottenere tutti i jar sul classpath e aprirli come file. È quindi possibile prendere questo elenco di file e creare una struttura dati contenente le classi.


0

usa Esperto delle dipendenze:

groupId: net.sf.extcos
artifactId: extcos
version: 0.4b

quindi usa questo codice:

ComponentScanner scanner = new ComponentScanner();
        Set classes = scanner.getClasses(new ComponentQuery() {
            @Override
            protected void query() {
                select().from("com.leyton").returning(allExtending(DynamicForm.class));
            }
        });

-2

Brent: il motivo per cui l'associazione è unidirezionale ha a che fare con il fatto che qualsiasi classe su qualsiasi componente di CLASSPATH può dichiararsi in qualsiasi pacchetto (eccetto java / javax). Quindi non c'è semplicemente nessuna mappatura di TUTTE le classi in un dato "pacchetto" perché nessuno sa né può saperlo. Potresti aggiornare un file jar domani e rimuovere o aggiungere classi. È come cercare di ottenere un elenco di tutte le persone chiamate John / Jon / Johan in tutti i paesi del mondo - nessuno di noi è onnisciente, quindi nessuno di noi avrà mai la risposta corretta.


Bella risposta filosofica, ma allora come funziona la scansione dei bean CDI? O come fa Hibernate a scansionare @Entities?
Ondra Žižka
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.