643 Stimmen

Können Sie mit Hilfe von Reflection alle Klassen in einem Paket finden?

Ist es möglich, alle Klassen oder Schnittstellen in einem bestimmten Paket zu finden? (Ein schneller Blick auf z.B. Package scheint das nicht der Fall zu sein.)

2 Stimmen

FYI die Lösung Amit Links zu funktioniert, obwohl es einen Fehler hat, wenn der Klassenpfad ein Leerzeichen in es hat (und wahrscheinlich für andere nicht-alphanumerische Zeichen zu). wenn Sie es in jeder Art von Produktionscode verwenden, siehe mein Kommentar zu seiner Antwort für eine Problemumgehung.

2 Stimmen

Beachten Sie auch diese Stelle .

1 Stimmen

Siehe dazugehörige Antwort: stackoverflow.com/a/30149061/4102160

42voto

BrainStone Punkte 2798

Hallo. Ich habe immer einige Probleme mit den obigen Lösungen (und auf anderen Seiten) gehabt.
Ich, als Entwickler, programmiere ein Addon für eine API. Die API verhindert die Verwendung von externen Bibliotheken oder Tools von Drittanbietern. Das Setup besteht außerdem aus einer Mischung aus Code in Jar- oder Zip-Dateien und Klassendateien, die sich direkt in einigen Verzeichnissen befinden. Mein Code musste also in der Lage sein, jedes Setup zu umgehen. Nach umfangreichen Recherchen habe ich eine Methode gefunden, die in mindestens 95 % aller möglichen Konfigurationen funktionieren wird.

Der folgende Code ist im Grunde die Overkill-Methode, die immer funktionieren wird.

Der Code:

Dieser Code durchsucht ein bestimmtes Paket nach allen Klassen, die darin enthalten sind. Er funktioniert nur für alle Klassen im aktuellen ClassLoader .

/**
 * Private helper method
 * 
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 * 
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 * 
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                        "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
    }

    return classes;
}

Diese drei Methoden bieten Ihnen die Möglichkeit, alle Klassen in einem bestimmten Paket zu finden.
Sie verwenden es so:

getClassesForPackage("package.your.classes.are.in");

Die Erklärung:

Die Methode ermittelt zunächst die aktuelle ClassLoader . Anschließend werden alle Ressourcen abgerufen, die dieses Paket enthalten, und diese durchlaufen URL s. Anschließend wird ein URLConnection und bestimmt, welche Art von URl wir haben. Es kann entweder ein Verzeichnis sein ( FileURLConnection ) oder ein Verzeichnis innerhalb einer jar- oder zip-Datei ( JarURLConnection ). Je nach Art der Verbindung werden zwei verschiedene Methoden aufgerufen.

Zunächst wollen wir sehen, was passiert, wenn es sich um eine FileURLConnection .
Es wird zunächst geprüft, ob die übergebene Datei existiert und ein Verzeichnis ist. Wenn dies der Fall ist, wird geprüft, ob es sich um eine Klassendatei handelt. Wenn ja, wird eine Class Objekt wird erstellt und in die ArrayList . Wenn es sich nicht um eine Klassendatei, sondern um ein Verzeichnis handelt, wird es einfach durchlaufen und das Gleiche getan. Alle anderen Fälle/Dateien werden ignoriert.

Wenn die URLConnection ist eine JarURLConnection wird die andere private Hilfsmethode aufgerufen. Diese Methode iteriert über alle Einträge im zip/jar-Archiv. Wenn ein Eintrag eine Klassendatei ist und sich innerhalb des Pakets ein Class Objekt wird erstellt und in der Datei ArrayList .

Nachdem alle Ressourcen geparst worden sind, gibt sie (die Hauptmethode) die ArrayList die alle Klassen im angegebenen Paket enthält, die der aktuelle ClassLoader weiß Bescheid.

Wenn der Prozess an irgendeiner Stelle fehlschlägt, wird ein ClassNotFoundException mit detaillierten Informationen über die genaue Ursache ausgelöst.

4 Stimmen

Dieses Beispiel scheint den Import von sun.net.www.protocol.file.FileURLConnection die zur Kompilierzeit eine Warnung erzeugt ("warning: sun.net.www.protocol.file.FileURLConnection is Sun proprietary API and may be removed in a future release"). Gibt es eine Alternative zur Verwendung dieser Klasse, oder kann die Warnung durch Anmerkungen unterdrückt werden?

0 Stimmen

Diese Methode funktioniert nicht für Bootstrap-Klassen, wie die in java.lang, java.util, ... Diese können gefunden werden, indem man System.getProperty("sun.boot.class.path") abruft, mit : oder ; (je nach Betriebssystem) trennt und dann leicht modifizierte Versionen der obigen checkDirectory und checkJarFile ausführt.

1 Stimmen

Sie können die Warnung/Fehlermeldung umgehen, indem Sie connection.getClass().getCanonicalName().equals( "sun.net.www.protocol.file.FileURLConnection" ) verwenden. Wenn Sie wirklich wollen, können Sie eine URLConnection erstellen, die Ihrer Meinung nach sun.net.www.protocol.file.FileURLConnection verwenden SOLLTE, und den Namen der Verbindungsklasse mit dem Namen der von Ihnen erstellten Klasse vergleichen. Wenn beide Namen gleich sind, können Sie die Verbindung als Instanz von sun.net.www.protocol.file.FileURLConnection behandeln, anstatt einen Fehler zu machen, falls sich der Klassenname ändert.

27voto

Luke Hutchison Punkte 6996

Der stabilste Mechanismus zur Auflistung aller Klassen in einem bestimmten Paket ist derzeit ClassGraph weil es die eine möglichst breite Palette von Mechanismen zur Angabe von Klassenpfaden einschließlich des neuen JPMS-Modulsystems. (Ich bin der Autor.)

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

0 Stimmen

Tolle Bibliothek! Bitte beachten Sie jedoch, dass es nicht zur Laufzeit für Android-Projekte funktioniert. Es gibt eine Möglichkeit, es zur Build-Zeit durch Gradle zu verwenden, aber.

1 Stimmen

@PaulW korrekt, und es gibt keine Pläne, jemals das dex/odex-Format der Dalvik-Laufzeitumgebung zu unterstützen, so dass das Scannen zur Build-Zeit die einzige Option ist. Bitte schlagen Sie Änderungen oder Verbesserungen an der Dokumentation vor, wenn Sie welche haben. github.com/classgraph/classgraph/wiki/Build-Time-Scanning

0 Stimmen

Funktioniert dies mit dem ehrwürdigen onejar-maven-plugin?

20voto

Williams López Punkte 181

Ohne zusätzliche Bibliotheken zu verwenden:

package test;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
        for(Class c:classes){
            System.out.println("Class: "+c);
        }
    }

    public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{

        String dottedPackage = pack.replaceAll("[/]", ".");
        List<Class> classes = new ArrayList<Class>();
        URL upackage = cl.getResource(pack);

        DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
        String line = null;
        while ((line = dis.readLine()) != null) {
            if(line.endsWith(".class")) {
               classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
            }
        }
        return classes;
    }
}

1 Stimmen

Wenn ich dies in einem JAR ausführe, upackage es null ... :(

1 Stimmen

Für ein Paket "com.mycompany.beans", ersetzen Sie "test" durch "com/mycompany/beans".

6 Stimmen

Ich erhalte eine Null, wenn ich diesen Code verwende. Scheint nur zu funktionieren, wenn Ihr jar eine ausführbare Datei ist

14voto

Sławek Punkte 1410

Im Allgemeinen erlauben Klassenlader nicht, alle Klassen im Klassenpfad zu durchsuchen. Aber normalerweise ist der einzige verwendete Klassenlader UrlClassLoader, von dem wir die Liste der Verzeichnisse und Jar-Dateien abrufen können (siehe getURLs ) und öffnen Sie diese nacheinander, um die verfügbaren Klassen aufzulisten. Dieser Ansatz, Klassenpfad-Scanning genannt, ist implementiert in Scannotation y Reflexionen .

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

Ein anderer Ansatz ist die Verwendung von Java Pluggable Annotation Processing API um einen Annotationsprozessor zu schreiben, der alle annotierten Klassen zur Kompilierzeit sammelt und die Indexdatei zur Laufzeit erstellt. Dieser Mechanismus ist implementiert in KlassenIndex Bibliothek:

// package-info.java
@IndexSubclasses
package my.package;

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

Beachten Sie, dass keine zusätzliche Einrichtung erforderlich ist, da das Scannen dank des Java-Compilers, der automatisch alle im Klassenpfad gefundenen Prozessoren erkennt, vollständig automatisiert ist.

0 Stimmen

Entdeckt dies die in einem Jar verpackten Klassen? Es scheint nicht zu funktionieren für mich.

0 Stimmen

Welches Tool Sie verwenden möchten?

0 Stimmen

Ich verwende die Reflections Lib. Aber ich habe es nach dem von @Aleksander Blomskøld erwähnten Workaround für neuere Versionen dieser Bibliothek zum Laufen gebracht.

10voto

tirz Punkte 1933

Wie wäre es damit:

public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
    final String pkgPath = pkgName.replace('.', '/');
    final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
    final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();

    Path root;
    if (pkg.toString().startsWith("jar:")) {
        try {
            root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
        } catch (final FileSystemNotFoundException e) {
            root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
        }
    } else {
        root = Paths.get(pkg);
    }

    final String extension = ".class";
    try (final Stream<Path> allPaths = Files.walk(root)) {
        allPaths.filter(Files::isRegularFile).forEach(file -> {
            try {
                final String path = file.toString().replace('/', '.');
                final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
                allClasses.add(Class.forName(name));
            } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
            }
        });
    }
    return allClasses;
}

Sie können die Funktion dann überladen:

public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
    return getClassesForPackage(pkg.getName());
}

Wenn Sie es testen müssen:

public static void main(final String[] argv) throws IOException, URISyntaxException {
    for (final Class<?> cls : getClassesForPackage("my.package")) {
        System.out.println(cls);
    }
    for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
        System.out.println(cls);
    }
}

Wenn Ihre IDE keine Importhilfe hat:

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

Es funktioniert:

  • von Ihrer IDE aus

  • für eine JAR-Datei

  • ohne externe Abhängigkeiten

3 Stimmen

Um unter Windows zu funktionieren, müssen Sie file.toString().replace('/', '.') mit final String path = file.toString().replace(' \\ , '.');

0 Stimmen

Dies sollte die einzige Antwort sein, und nur eine Antwort!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X