Wie lese ich alle Klassen aus einem Java-Paket im Klassenpfad?

94

Ich muss Klassen lesen, die in einem Java-Paket enthalten sind. Diese Klassen befinden sich im Klassenpfad. Ich muss diese Aufgabe direkt von einem Java-Programm aus erledigen. Kennen Sie einen einfachen Weg?

List<Class> classes = readClassesFrom("my.package")
Jørgen R.
quelle
7
Einfach gesagt, nein, das geht nicht so einfach. Es gibt einige extrem lange Tricks, die in bestimmten Situationen funktionieren, aber ich empfehle dringend ein anderes Design.
Skaffman
Die Lösung könnte im Weld- Projekt gefunden werden.
Ondra Žižka
Siehe diesen Link für die Antwort: stackoverflow.com/questions/176527/…
Karthik E

Antworten:

48

Wenn Sie Spring in Ihrem Klassenpfad haben, wird dies von den folgenden ausgeführt.

Suchen Sie alle Klassen in einem Paket, die mit XmlRootElement versehen sind:

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;
}
Shalom938
quelle
Danke für das Code-Snippet. :) Wenn Sie Klassenüberschneidungen in einer Kandidatenliste vermeiden möchten, ändern Sie den Sammlungstyp von Liste in Festlegen. Verwenden Sie hasEnclosingClass () und getEnclosingClassName () von classMetaData. Verwenden Sie die getEnclosingClass-Methode für eine zugrunde liegende Klasse
Traeper
29

Sie können das hier beschriebene Reflections-Projekt verwenden

Es ist ziemlich vollständig und einfach zu bedienen.

Kurzbeschreibung von der obigen Website:

Reflections durchsucht Ihren Klassenpfad, indiziert die Metadaten, ermöglicht die Abfrage zur Laufzeit und kann diese Informationen für viele Module in Ihrem Projekt speichern und sammeln.

Beispiel:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);
Renato
quelle
Funktioniert Equinox? Und wenn ja, kann dies ohne Aktivierung von Plugins geschehen?
Tag
funktioniert für Tagundnachtgleiche. Fügen Sie in älteren Versionen von Reflections das Bundle jar vfsType hinzu. siehe hier
zapp
28

Ich benutze dieses, es funktioniert mit Dateien oder JAR-Archiven

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;
}
Edoardo Panfili
quelle
1
Dies funktioniert, wenn Sie den Verweis auf File.Separator entfernen und einfach '/'
Will Glass
1
Eine weitere Änderung ist erforderlich, damit dies mit JAR-Dateien funktioniert, wenn der Pfad Leerzeichen enthält. Sie müssen den JAR-Dateipfad dekodieren. Änderung: jarFileName = packageURL.getFile (); an: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass
Ich bekomme nur den Paketnamen in jar. Ich bekomme keinen Klassennamen
AutoMEta
Neue Datei (uri) behebt Ihr Problem mit Leerzeichen.
Trejkaz
entryName.lastIndexOf ('.') wäre -1
Marstone
11

Spring hat eine hervorragende Suchfunktion für Klassenpfade in der implementiert PathMatchingResourcePatternResolver. Wenn Sie das classpath*Präfix: verwenden, können Sie alle Ressourcen, einschließlich Klassen in einer bestimmten Hierarchie, finden und bei Bedarf sogar filtern. Dann können Sie die Kinder verwenden AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilterund AssignableTypeFilterdiese Ressourcen entweder auf Klassenebene Anmerkungen oder auf Schnittstellen implementieren sie zu filtern.

John Ellinwood
quelle
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[]{});
}

Diese Lösung wurde in der EJB- Umgebung getestet .

Paul Kuit
quelle
6

Scannotation und Reflections verwenden den Scanansatz für Klassenpfade:

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

Ein anderer Ansatz besteht darin, die Java Pluggable Annotation Processing API zu verwenden, um einen Annotationsprozessor zu schreiben, der alle kommentierten Klassen zur Kompilierungszeit sammelt und die Indexdatei für die Laufzeit erstellt. Dieser Mechanismus ist in der ClassIndex- Bibliothek implementiert :

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Sławek
quelle
3

Soweit ich weiß, fehlt diese Funktionalität in der Java Reflection API immer noch verdächtig. Sie können ein Paketobjekt erhalten, indem Sie einfach Folgendes tun:

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

Aber wie Sie wahrscheinlich bemerkt haben, können Sie damit die Klassen in diesem Paket nicht auflisten. Ab sofort müssen Sie einen Dateisystem-orientierten Ansatz wählen.

Ich habe in diesem Beitrag einige Beispielimplementierungen gefunden

Ich bin nicht 100% sicher, ob diese Methoden funktionieren, wenn Ihre Klassen in JAR-Dateien vergraben sind, aber ich hoffe, dass eine davon dies für Sie erledigt.

Ich stimme @skaffman zu ... wenn Sie eine andere Vorgehensweise haben, würde ich empfehlen, dies stattdessen zu tun.

Brent schreibt Code
quelle
4
Es ist nicht verdächtig, es funktioniert einfach nicht so. Klassen "gehören" nicht zu Paketen, sie haben Verweise auf sie. Der Verein zeigt nicht in die andere Richtung.
Skaffman
1
@skaffman Sehr interessanter Punkt. Ich habe nie so darüber nachgedacht. Also, solange wir diesen Gedankengang beschreiten, warum ist die Assoziation nicht bidirektional (dies ist jetzt mehr für meine eigene Neugier)?
Brent schreibt Code
3

Der robusteste Mechanismus zum Auflisten aller Klassen in einem bestimmten Paket ist derzeit ClassGraph , da er die größtmögliche Anzahl von Klassenpfadspezifikationsmechanismen verarbeitet , einschließlich des neuen JPMS-Modulsystems. (Ich bin der Autor.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}
Luke Hutchison
quelle
Ich bin erst ein paar Jahre später darauf gestoßen. Dies ist ein großartiges Werkzeug! Es funktioniert viel schneller als Reflexion und ich mag es, dass es keine statischen Initialisierer aufruft. Genau das, was ich brauchte, um ein Problem zu lösen, das wir hatten.
Akagixxer
2

eXtcos sieht vielversprechend aus. Stellen Sie sich vor, Sie möchten alle Klassen finden, die:

  1. Erweitern Sie die Klasse "Component" und speichern Sie sie
  2. Sind mit "MyComponent" und versehen
  3. Sind im "gemeinsamen" Paket.

Mit eXtcos ist dies so einfach wie

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));
    }
});
Noelob
quelle
2
  1. Bill Burke hat einen (schönen Artikel über das Scannen von Klassen) geschrieben und dann Scannotation geschrieben .

  2. Hibernate hat dies bereits geschrieben:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI könnte dies lösen, weiß es aber nicht - habe es noch nicht vollständig untersucht

.

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

Auch für Anmerkungen:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}
Ondra Žižka
quelle
Der Link zum Artikel ist tot.
user2418306
1

Ich habe es zufällig implementiert und es funktioniert in den meisten Fällen. Da es lang ist, habe ich es hier in eine Datei eingefügt .

Die Idee ist, den Speicherort der Klassenquelldatei zu finden, der in den meisten Fällen verfügbar ist (eine bekannte Ausnahme sind JVM-Klassendateien - soweit ich getestet habe). Wenn sich der Code in einem Verzeichnis befindet, durchsuchen Sie alle Dateien und nur Spotklassendateien. Wenn sich der Code in einer JAR-Datei befindet, scannen Sie alle Einträge.

Diese Methode kann nur angewendet werden, wenn:

  1. Sie haben eine Klasse, die sich in demselben Paket befindet, das Sie entdecken möchten. Diese Klasse wird als SeedClass bezeichnet. Wenn Sie beispielsweise alle Klassen in 'java.io' auflisten möchten, kann dies die Startklasse sein java.io.File.

  2. Ihre Klassen befinden sich in einem Verzeichnis oder in einer JAR-Datei, die Informationen zur Quelldatei enthält (keine Quellcodedatei, sondern nur eine Quelldatei). Soweit ich es versucht habe, funktioniert es fast zu 100% mit Ausnahme der JVM-Klasse (diese Klassen werden mit der JVM geliefert).

  3. Ihr Programm muss über die Berechtigung zum Zugriff auf ProtectionDomain dieser Klassen verfügen. Wenn Ihr Programm lokal geladen ist, sollte es kein Problem geben.

Ich habe das Programm nur für meinen regulären Gebrauch getestet, daher kann es immer noch Probleme geben.

Ich hoffe das hilft.

NawaMan
quelle
1
es scheint ein sehr interessantes Zeug zu sein! Ich werde versuchen, es zu benutzen. Kann ich Ihren Code in einem Open Source-Projekt verwenden, wenn ich ihn für hilfreich befunden habe?
1
Ich bereite mich darauf vor, es auch als Open Source zu veröffentlichen. Also los: D
NawaMan
@NawaMan: Hast du es endlich eröffnet? Wenn ja: Wo finden wir die letzte Version? Vielen Dank!
Joanis
1

Hier ist eine weitere Option, eine geringfügige Änderung an einer anderen Antwort oben / unten:

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

Damals, als Applets an der Tagesordnung waren, hatte man möglicherweise eine URL im Klassenpfad. Wenn der Klassenladeprogramm eine Klasse benötigt, durchsucht er alle Speicherorte im Klassenpfad, einschließlich der http-Ressourcen. Da der Klassenpfad URLs und Verzeichnisse enthalten kann, gibt es keine einfache Möglichkeit, eine endgültige Liste der Klassen zu erhalten.

Sie können jedoch ziemlich nahe kommen. Einige der Spring-Bibliotheken tun dies jetzt. Sie können alle Jars in den Klassenpfad aufnehmen und sie wie Dateien öffnen. Sie können dann diese Liste von Dateien nehmen und eine Datenstruktur erstellen, die Ihre Klassen enthält.

Brianegge
quelle
0

Verwenden Sie den Abhängigkeits-Maven:

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

Verwenden Sie dann diesen Code:

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

Brent - der Grund, warum die Zuordnung eine Möglichkeit ist, hat damit zu tun, dass sich jede Klasse in einer beliebigen Komponente Ihres CLASSPATH in einem beliebigen Paket deklarieren kann (außer in Java / Javax). Daher gibt es einfach keine Zuordnung ALLER Klassen in einem bestimmten "Paket", da niemand weiß oder wissen kann. Sie können morgen eine JAR-Datei aktualisieren und Klassen entfernen oder hinzufügen. Es ist, als würde man versuchen, eine Liste aller Menschen mit dem Namen John / Jon / Johan in allen Ländern der Welt zu bekommen - keiner von uns ist allwissend, daher wird keiner von uns jemals die richtige Antwort haben.

Idarwin
quelle
Schöne philosophische Antwort, aber wie funktioniert das Scannen von CDI-Beans dann? Oder wie scannt Hibernate @Entities?
Ondra Žižka