it-swarm.com.de

Wie kann ich alle Klassen in einem Paket auflisten und sie einer Liste hinzufügen?

Ich muss alle Klassen in einem Paket auflisten und sie einer Liste hinzufügen. Die nicht dynamische Version für eine einzelne Klasse sieht folgendermaßen aus:

List allClasses = new ArrayList();
allClasses.add(String.class);

Wie kann ich dies dynamisch tun, um alle Klassen in einem Paket und alle seine Unterpakete hinzuzufügen?


Update: Nachdem ich die ersten Antworten gelesen habe, ist es absolut wahr, dass ich versuche, ein weiteres sekundäres Problem zu lösen. Und ich weiß, dass dies möglich ist, da andere Tools dies tun. Siehe neue Frage hier

Update: Beim nochmaligen Lesen kann ich sehen, wie es falsch gelesen wird. Ich freue mich darauf, alle Klassen von MY PROJECT nach der Kompilierung aus dem Dateisystem aufzulisten. 

31
thvo

**** UPDATE 1 (2012) ****

OK, ich habe es endlich geschafft, das Code-Snippet unten aufzuräumen. Ich habe es in sein eigenes Github-Projekt gesteckt und sogar Tests hinzugefügt.

https://github.com/ddopson/Java-class-enumerator

**** UPDATE 2 (2016) ****

Einen noch robusteren und funktionsreicheren Klassenpfad-Scanner finden Sie unter https://github.com/lukehutch/fast-classpath-scanner/wiki . Ich würde empfehlen, zuerst mein Code-Snippet zu lesen, um ein umfassendes Verständnis zu erlangen, und dann das Tool von Lukehutch zu Produktionszwecken verwenden.

**** ursprünglicher Beitrag (2010) ****

Streng genommen ist es nicht möglich, die Klassen in einem package aufzulisten. Dies liegt daran, dass ein Paket wirklich nicht mehr als ein Namespace ist (z. B. com.epicapplications.foo.bar), und jede JAR-Datei im Klassenpfad könnte möglicherweise Klassen zu einem Paket hinzufügen. Schlimmer noch, der Classloader lädt Klassen bei Bedarf und ein Teil des Klassenpfads befindet sich möglicherweise auf der anderen Seite einer Netzwerkverbindung.

Es ist möglich, ein restriktiveres Problem zu lösen. ZB alle Klassen in einer JAR-Datei oder alle Klassen, die eine JAR-Datei in einem bestimmten Paket definiert. Dies ist ohnehin das häufigere Szenario.

Leider gibt es keinen Rahmencode, um diese Aufgabe zu vereinfachen. Sie müssen das Dateisystem auf ähnliche Weise prüfen, wie ClassLoader nach Klassendefinitionen suchen würde.

Im Web gibt es viele Beispiele für Klassendateien in einfachen alten Verzeichnissen. Die meisten von uns arbeiten heutzutage mit JAR-Dateien.

Probieren Sie Folgendes aus, um die Arbeit mit JAR-Dateien zu erleichtern ...

private static ArrayList<Class<?>> getClassesForPackage(Package pkg) {
    String pkgname = pkg.getName();
    ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
    // Get a File object for the package
    File directory = null;
    String fullPath;
    String relPath = pkgname.replace('.', '/');
    System.out.println("ClassDiscovery: Package: " + pkgname + " becomes Path:" + relPath);
    URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
    System.out.println("ClassDiscovery: Resource = " + resource);
    if (resource == null) {
        throw new RuntimeException("No resource for " + relPath);
    }
    fullPath = resource.getFile();
    System.out.println("ClassDiscovery: FullPath = " + resource);

    try {
        directory = new File(resource.toURI());
    } catch (URISyntaxException e) {
        throw new RuntimeException(pkgname + " (" + resource + ") does not appear to be a valid URL / URI.  Strange, since we got it from the system...", e);
    } catch (IllegalArgumentException e) {
        directory = null;
    }
    System.out.println("ClassDiscovery: Directory = " + directory);

    if (directory != null && directory.exists()) {
        // Get the list of the files contained in the package
        String[] files = directory.list();
        for (int i = 0; i < files.length; i++) {
            // we are only interested in .class files
            if (files[i].endsWith(".class")) {
                // removes the .class extension
                String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6);
                System.out.println("ClassDiscovery: className = " + className);
                try {
                    classes.add(Class.forName(className));
                } 
                catch (ClassNotFoundException e) {
                    throw new RuntimeException("ClassNotFoundException loading " + className);
                }
            }
        }
    }
    else {
        try {
            String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
            JarFile jarFile = new JarFile(jarPath);         
            Enumeration<JarEntry> entries = jarFile.entries();
            while(entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();
                if(entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) {
                    System.out.println("ClassDiscovery: JarEntry: " + entryName);
                    String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
                    System.out.println("ClassDiscovery: className = " + className);
                    try {
                        classes.add(Class.forName(className));
                    } 
                    catch (ClassNotFoundException e) {
                        throw new RuntimeException("ClassNotFoundException loading " + className);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
        }
    }
    return classes;
}
37
Dave Dopson

Ich habe herausgefunden, wie das geht. Hier ist das Verfahren:

  1. Beginnen Sie mit einer Klasse im Stammpaket, und rufen Sie den Ordner, in dem sich der Klassenlader befindet
  2. Zählen Sie alle .class-Dateien in diesem Ordner rekursiv auf
  3. Konvertieren Sie die Dateinamen in vollständig qualifizierte Klassennamen
  4. Verwenden Sie Class.forName (), um die Klassen abzurufen

Es gibt ein paar böse Tricks, die mich etwas unruhig machen, aber es funktioniert - zum Beispiel:

  1. Konvertieren von Pfadnamen in Paketnamen mithilfe der Zeichenfolgenmanipulation
  2. Festlegen des Root-Paketnamens, um das Pfadpräfix zu entfernen

Schade, dass Stackoverflow es mir nicht erlaubt, meine eigene Antwort zu akzeptieren ...

3
thvo

Sie könnten meine Bibliothek FastClasspathScanner ausprobieren.

Führen Sie die folgenden Schritte aus, um alle Klassen in einem Paket aufzulisten:

Set<String> classNames = new FastClassPathScanner("com.mypackage")
    .scan()
    .getNamesOfAllClasses();
3
Luke Hutchison

Ich fürchte, Sie müssen den Klassenpfad und die anderen Stellen, an denen Java nach Klassen sucht (z. B. das ext-Verzeichnis oder den Boot-Klassenpfad), manuell scannen. Da Java das langsame Laden von Klassen verwendet, weiß es möglicherweise nicht einmal Informationen zu zusätzlichen Klassen in Ihren Paketen, die noch nicht geladen wurden ..__ Überprüfen Sie auch den Begriff "versiegelter" Pakete.

2
G B

Es ist komisch, dass diese Frage immer wieder auftaucht. Das Problem ist, dass dieses Schlüsselwort geeigneter als "Namespace" bezeichnet wurde. Das Java-Paket beschreibt keinen konkreten Container, der alle Klassen im Paket auf einmal enthält. Es definiert einfach ein Token, mit dem Klassen angeben können, dass sie Mitglied dieses Pakets sind. Sie müssen den gesamten Klassenpfad durchsuchen (wie in einer anderen Antwort angegeben), um alle Klassen in einem Paket zu ermitteln.

2
David M. Karr

Hier gibt es eine Einschränkung: ApplicationEngines/Servlet-Container wie Tomcat und JBoss haben hierarchische Klassenlader . Das Laden des Systemklassenladers reicht nicht aus.

Die Funktionsweise von Tomcat (die Dinge haben sich möglicherweise geändert, aber meine derzeitigen Erfahrungen lassen mich nicht anders glauben), aber jeder Anwendungskontext verfügt über einen eigenen Klassenlader, sodass die Klassen für die Anwendung 'foo' nicht mit Klassen kollidieren für die Bewerbung 'fooV2'

Nur als Beispiel. Wenn alle Klassen in einen übergeordneten Klassenkontext eingebunden wurden, hätten Sie keine Ahnung, ob Sie Klassen verwenden würden, die für Version 1 oder Version 2 geeignet sind. 

Darüber hinaus muss jeder auf Systemklassen wie Java.lang.String zugreifen. Dies ist die Hierarchie. Es prüft zuerst den lokalen App-Kontext und bewegt sich nach oben (dies ist meine aktuelle Situation im Übrigen).

Um dies zu erreichen, wäre ein besserer Ansatz: this.getClass (). GetClassloader ()

In meinem Fall habe ich einen Webservice, der für einige Module eine Selbstermittlung durchführen muss, und diese befinden sich offensichtlich in diesem Webservice-Kontext oder im Systemkontext. Wenn ich das oben erwähnte, kann ich beides überprüfen. Indem ich einfach den System-Classloader bekomme, bekomme ich keinen Zugriff auf die Anwendungsklassen (und meine Ressourcen sind daher null).

2

Schauen Sie sich an, was Java.net.URLClassLoader macht. Es listet niemals Klassen auf, sondern versucht nur, Klassen zu finden, wenn Sie danach gefragt werden. Wenn Sie die Klassen auflisten möchten, müssen Sie den Klassenpfad abrufen, ihn in Verzeichnisse und JAR-Dateien aufteilen. Durchsuchen Sie die Verzeichnisse (und ihre Unterverzeichnisse) und JAR-Dateien nach Dateien mit dem Namen * .class .

Es kann sich lohnen, Open-Source-Projekte anzuschauen, die scheinbar die gewünschte Aufzählung (wie Eclipse ) zur Inspiration bieten.

0
Diastrophism

Wenn Sie lediglich eine Gruppe zusammengehöriger Klassen laden möchten, kann Spring Ihnen dabei helfen.

Spring kann eine Liste oder eine Karte aller Klassen instanziieren, die eine bestimmte Schnittstelle in einer Codezeile implementieren. Die Liste oder Map enthält Instanzen aller Klassen, die diese Schnittstelle implementieren.

Als Alternative zum Laden der Klassenliste aus dem Dateisystem implementieren Sie stattdessen dieselbe Schnittstelle in allen Klassen, die Sie laden möchten, unabhängig vom Paket. Auf diese Weise können Sie alle gewünschten Klassen laden (und instanziieren), unabhängig davon, in welchem ​​Paket sie sich befinden.

Wenn Sie dagegen alle in einem Paket haben, wollen Sie einfach, dass alle Klassen in diesem Paket eine bestimmte Schnittstelle implementieren.

0