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

528

Ist es möglich, alle Klassen oder Schnittstellen in einem bestimmten Paket zu finden? (Ein kurzer Blick auf zB Packagescheint es nein zu sein.)

Jonik
quelle
2
Zu Ihrer Information, die Lösung, mit der Amit verknüpft ist, funktioniert, obwohl es einen Fehler gibt, wenn der Klassenpfad ein Leerzeichen enthält (und wahrscheinlich auch für andere nicht alphanumerische Zeichen). Wenn Sie es in einem Produktionscode verwenden, finden Sie eine Problemumgehung in meinem Kommentar zu seiner Antwort.
Kip
2
Beachten Sie auch diesen Beitrag .
Barfuin
1
Siehe verwandte Antwort: stackoverflow.com/a/30149061/4102160
Cfx
1
Beachten Sie auch diesen Beitrag .
sp00m
1
Siehe meine Antwort unten zu ClassGraph. Es ist derzeit die robusteste Methode zum Scannen des Klassenpfads und des Modulpfads.
Luke Hutchison

Antworten:

374

Aufgrund der Dynamik von Klassenladern ist dies nicht möglich. Klassenlader müssen der VM nicht mitteilen, welche Klassen sie bereitstellen kann, sondern sie erhalten lediglich Anforderungen für Klassen und müssen eine Klasse zurückgeben oder eine Ausnahme auslösen.

Wenn Sie jedoch Ihre eigenen Klassenlader schreiben oder die Klassenpfade und ihre Gläser untersuchen, können Sie diese Informationen finden. Dies erfolgt jedoch über Dateisystemoperationen und nicht über Reflektion. Möglicherweise gibt es sogar Bibliotheken, die Ihnen dabei helfen können.

Wenn Klassen generiert oder remote bereitgestellt werden, können Sie diese Klassen nicht erkennen.

Die normale Methode besteht stattdessen darin, die Klassen, auf die Sie Zugriff benötigen, irgendwo in einer Datei zu registrieren oder sie in einer anderen Klasse zu referenzieren. Oder verwenden Sie einfach die Konvention, wenn es um die Benennung geht.

Nachtrag: In der Reflections Library können Sie Klassen im aktuellen Klassenpfad nachschlagen. Es kann verwendet werden, um alle Klassen in einem Paket zu erhalten:

 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);
Staale
quelle
12
Die Unfähigkeit, nach Klassennamen zu fragen, hat mich lange nervt. Sicher, es ist schwierig und die Leistung kann stark variieren, und für bestimmte Klassenlader ist die Liste undefiniert oder unbegrenzt, aber es gibt Möglichkeiten, wie dies hätte umgangen werden können.
Mr. Shiny und New
16
Beachten Sie, dass diese Lösung nicht funktioniert, da getSubTypesOf standardmäßig keine Untertypen von Object zurückgibt. Informationen zur Konfiguration des SubTypeScanners finden Sie in der Lösung von Aleksander Blomskøld.
Alex Spurling
17
Reflexionen erfordern Guave. Guave ist groß. Version 14.0.1 ist 2.1MB.
Mike Jones
3
Hat bei mir nicht funktioniert. Mac OSX - Reflections Dependency Version 0.9.9-RC1 (Maven) - JDK 1.7. Überdenken Sie die akzeptierte Antwort. @ AleksanderBlomskøld Antwort ist die richtige. !!!!!
Konstantinos Margaritis
68
Wenn dies eine leere Liste zurückgibt, initialisieren Sie das Reflections-Objekt wie folgt: Reflectionsreflexions = new Reflections ("your.package.here", new SubTypesScanner (false));
João Rocha da Silva
186

Sie sollten sich wahrscheinlich die Open Source Reflections-Bibliothek ansehen . Damit können Sie ganz einfach erreichen, was Sie wollen.

Richten Sie zunächst den Reflexionsindex ein (es ist etwas chaotisch, da die Suche nach allen Klassen standardmäßig deaktiviert ist):

List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

Anschließend können Sie alle Objekte in einem bestimmten Paket abfragen:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
Aleksander Blomskøld
quelle
6
Ah, los geht's: code.google.com/p/reflections/issues/detail?id=122 . Das Objekt ist standardmäßig ausgeschlossen, Sie können es jedoch erneut auslösen. Vielen Dank, dass Sie mich auf diese Bibliothek hingewiesen haben. Es ist großartig!
mtrc
1
Ich bin auf meinem Mac auf Probleme mit diesem Code gestoßen (im Zusammenhang mit nativen Bibliotheken), aber die Verwendung .addUrls(ClasspathHelper.forJavaClassPath())anstelle des oben genannten hat sie für mich gelöst. Weniger Code auch!
David Pärsson
3
Wenn sich jemand fragt, ob der einfachste Weg, das Standardpaket zu erhalten, darin besteht, dass das Präfix eine leere Zeichenfolge ist -> "".
JBA
2
Die Bibliothek "Reflections" hat eine knifflige Lizenz: github.com/ronmamo/reflections/blob/master/COPYING.txt . Der Trick ist, dass die Lizenz nur die Lizenz selbst kostenlos nutzen kann. Um die Bibliothek (nicht die Lizenz) wirklich nutzen zu können, muss sich jeder an den Autor wenden und die Nutzungsbedingungen aushandeln.
Serge Rogatch
3
Serge, ich denke du verstehst WTFPL falsch: wtfpl.net Ich denke WTFPL bedeutet, dass du frei tun kannst, was du willst, nicht nur mit der Lizenz, sondern auch mit dem Code
Richo
122

Google Guava 14 enthält eine neue Klasse ClassPathmit drei Methoden zum Scannen nach Klassen der obersten Ebene:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

Weitere Informationen finden Sie in den ClassPathJavadocs .

Christoph Leiter
quelle
Dies funktionierte für mich, wo Reflection nicht konnte (kein gemeinsamer direkter Vorfahr, keine gemeinsame Anmerkung)
Riccardo Cossu
1
Wie ich in einem Kommentar unten erwähnt habe , ClassPathist mit markiert @Beta, so ist möglicherweise keine gute Idee für einige ...
Christian
1
Zu sagen, dass dies dort funktioniert, wo Reflektion nicht funktioniert, ist etwas seltsam. Die Lösung wird zweifellos mithilfe der Reflection- (und Class Loader-) Funktionalität implementiert.
Maarten Bodewes
5
Ich denke, er meinte die in der anderen Antwort erwähnte Reflections-Bibliothek.
Christoph Leiter
Funktioniert unter Java 11, wenn Guava Version 28.1-jre verwendet wird.
Gorjanz
112

Sie können diese Methode 1 verwenden, die das verwendet ClassLoader.

/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

__________
1 Diese Methode wurde ursprünglich von http://snippets.dzone.com/posts/show/4831 übernommen , das vom Internetarchiv archiviert wurde, wie jetzt verlinkt. Das Snippet ist auch unter https://dzone.com/articles/get-all-classes-within-package verfügbar .

Ahmed Ashour
quelle
6
Ich hatte ein Problem damit, wenn mein Pfad Leerzeichen enthielt. Die URL-Klasse hat Leerzeichen entfernt %20, aber der new File()Konstruktor hat dies als Literal-Prozentzeichen zwei Null behandelt. Ich habe es behoben, indem ich die dirs.add(...)Zeile wie folgt geändert habe: dirs.add(new File(resource.toURI())); Dies bedeutete auch, dass ich URISyntaxExceptiondie Throws-Klausel vongetClasses
Kip
19
Sie haben gerade von dzone.com/articles/get-all-classes-within-package kopiert ! Bitte beziehen Sie sich beim nächsten Mal auf die Quelle
RS
19
+1, da für diese Lösung KEINE externen Bibliotheken erforderlich sind ... Koppeln Sie Ihren Code NIEMALS, NIEMALS zufällig mit Bibliotheken, nur um eine kleine Sache wie diese zu erreichen. Wissen Sie, dass Sie potenzielle Angriffsfläche für Angreifer hinzufügen? 2015 November ein Apache Commons Problem entdeckt , dass führt zu Remote - Ausführung Befehl nur von Apache Commons, die in dem Classpath einer App zum Einsatz auf Jboss / Weblogic [ foxglovesecurity.com/2015/11/06/...
sc0p
@Qix hat richtig bemerkt, dass dieser Code jar nicht unterstützt. Um Jars & Verzeichnisse zu unterstützen . Der Code wurde wie
folgt
1
Gute Lösung, aber es scheint besser zu sein, wenn 'Class.forName (String className)' durch 'Class.forName (String className, boolesche Initialisierung, ClassLoader-Loader)' ersetzt wird, wobei 'initialize = false;' um keine Klasseninstanzen zu erstellen.
Andrei_N
99

Frühling

Dieses Beispiel gilt für Spring 4, aber Sie können den Klassenpfad-Scanner auch in früheren Versionen finden.

// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

Google Guava

Hinweis: In Version 14 ist die API weiterhin als @Beta markiert. Achten Sie daher im Produktionscode darauf.

final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}
voho
quelle
5
Hervorragende Antwort. Hier gibt es zu viele Lösungen, die ausführlich, nicht getestet und nicht funktionsfähig sind! Dieser ist fantastisch: Er ist prägnant und getestet (er stammt aus Guave). Sehr gut! Es ist nützlich, es verdient mehr Gegenstimmen.
JeanValjean
Leider ist die ClassPathKlasse in Guava auch gekennzeichnet mit @Beta: "APIs, die auf Klassen- oder Methodenebene mit der Annotation @Beta gekennzeichnet sind, können sich ändern. Sie können in jeder Hauptversion auf irgendeine Weise geändert oder sogar entfernt werden. Wenn Ihr Code Ist eine Bibliothek selbst (dh sie wird auf dem CLASSPATH von Benutzern außerhalb Ihrer eigenen Kontrolle verwendet), sollten Sie keine Beta-APIs verwenden, es sei denn, Sie verpacken sie neu ... " code.google.com/p/guava-libraries/#Important_Warnings
Christian
@Christian Guter Punkt, ich habe es nicht bemerkt! Vielen Dank. Ich werde eine weitere Antwort mit dem Spring Classpath Scanner hinzufügen, der mit Sicherheit nicht Beta ist.
Voho
Um verschachtelte statische Klassen mithilfe der Guavenlösung zu finden, getAllClasses()kann die Methode verwendet werden.
v.ladynev
1
Die Spring-Lösung funktioniert nur, wenn sie von einem ausführbaren JAR ausgeführt wird.
Luke
38

Hallo. Ich hatte immer einige Probleme mit den oben genannten Lösungen (und auf anderen Websites).
Ich als Entwickler programmiere ein Addon für eine API. Die API verhindert die Verwendung externer Bibliotheken oder Tools von Drittanbietern. Das Setup besteht auch 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, um jedes Setup herum zu arbeiten. Nach vielen Recherchen habe ich eine Methode gefunden, die in mindestens 95% aller möglichen Setups funktioniert.

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

Der Code:

Dieser Code durchsucht ein bestimmtes Paket nach allen darin enthaltenen Klassen. Es funktioniert nur für alle Klassen in der 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 erhält zuerst den aktuellen Wert ClassLoader. Es ruft dann alle Ressourcen ab, die das Paket enthalten, und iteriert diese URLs. Es erstellt dann eine URLConnectionund bestimmt, welche Art von URl wir haben. Es kann entweder ein Verzeichnis ( FileURLConnection) oder ein Verzeichnis in einer JAR- oder ZIP-Datei ( JarURLConnection) sein. Je nachdem, welche Art von Verbindung wir haben, werden zwei verschiedene Methoden aufgerufen.

Lassen Sie uns zuerst sehen, was passiert, wenn es ein ist FileURLConnection.
Zunächst wird geprüft, ob die übergebene Datei vorhanden ist und ein Verzeichnis ist. In diesem Fall wird geprüft, ob es sich um eine Klassendatei handelt. In Classdiesem Fall wird ein Objekt erstellt und in das Feld eingefügt ArrayList. Wenn es sich nicht um eine Klassendatei handelt, sondern um ein Verzeichnis, iterieren wir einfach hinein und machen dasselbe. Alle anderen Fälle / Dateien werden ignoriert.

Wenn dies URLConnectioneine ist, wird JarURLConnectiondie andere private Hilfsmethode aufgerufen. Diese Methode durchläuft alle Einträge im zip / jar-Archiv. Wenn ein Eintrag eine Klassendatei ist und sich innerhalb des Pakets befindet, wird ein ClassObjekt erstellt und im gespeichert ArrayList.

Nachdem alle Ressourcen analysiert wurden, gibt es (die Hauptmethode) den ArrayListInhalt aller Klassen im angegebenen Paket zurück, über die der aktuelle ClassLoaderBescheid weiß.

Wenn der Prozess zu irgendeinem Zeitpunkt fehlschlägt, ClassNotFoundExceptionwird a mit detaillierten Informationen über die genaue Ursache ausgelöst.

BrainStone
quelle
4
Dieses Beispiel scheint einen Import zu erfordern sun.net.www.protocol.file.FileURLConnection, der zur Kompilierungszeit eine Warnung generiert ("Warnung: sun.net.www.protocol.file.FileURLConnection ist eine proprietäre Sun-API und wird möglicherweise in einer zukünftigen Version entfernt"). Gibt es eine Alternative zur Verwendung dieser Klasse oder kann die Warnung mithilfe von Anmerkungen unterdrückt werden?
Christian
Diese Methode funktioniert nicht für Bootstrap-Klassen wie java.lang, java.util, ... Diese können gefunden werden, indem System.getProperty ("sun.boot.class.path") abgerufen und mit: oder; (abhängig vom Betriebssystem) und dann leicht modifizierte Versionen des obigen checkDirectory und checkJarFile ausführen.
Coderforlife
1
Sie können die Warnung / den Fehler umgehen, indem Sie connection.getClass (). GetCanonicalName (). Equals ("sun.net.www.protocol.file.FileURLConnection") verwenden. Wenn Sie wirklich möchten, 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 gleich sind, können Sie es als eine Instanz von sun.net.www.protocol.file.FileURLConnection behandeln, anstatt zu scheitern, falls sich der Klassenname ändert.
William Deans
1
@Christian Sie können vermeiden, dass die FileURLConnection so etwas tut: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }Es ist das, was ich in meinem Code getan habe, um mit JPA kommentierte Klassen zu durchsuchen
Zardoz89
15

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;
    }
}
Williams López
quelle
Wenn ich dies in einem JAR laufen lasse, upackageist null... :(
Christian
Für ein Paket "com.mycompany.beans" ersetzen Sie "test" durch "com / mycompany / beans"
James Jithin
4
Ich erhalte eine Null, wenn ich diesen Code verwende. Scheint nur zu funktionieren, wenn Ihr Glas eine ausführbare Datei ist
Alao
Wenn Sie den Paketnamen von erhalten haben String pack = getPackage().getName();, müssen Sie hinzufügenpack = pack.replaceAll("[.]", "/");
user2682877
13

Im Allgemeinen erlauben Klassenlader nicht das Durchsuchen aller Klassen im Klassenpfad. Normalerweise ist der einzige verwendete Klassenladeprogramm UrlClassLoader, aus dem wir die Liste der Verzeichnisse und JAR-Dateien (siehe getURLs ) abrufen und einzeln öffnen können, um die verfügbaren Klassen aufzulisten . Dieser Ansatz, der als Klassenpfad-Scannen bezeichnet wird, ist in Scannotation and Reflections implementiert .

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 :

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

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

Beachten Sie, dass kein zusätzliches Setup erforderlich ist, da das Scannen vollständig automatisiert ist, da der Java-Compiler automatisch alle im Klassenpfad gefundenen Prozessoren erkennt.

Sławek
quelle
entdeckt dies Klassen, die in einem Glas verpackt sind? Es scheint bei mir nicht zu funktionieren.
Asgs
Welches Tool versuchen Sie zu verwenden?
Sławek
Ich benutze die Reflections lib. Aber ich habe es zum Laufen gebracht, nachdem ich die von @Aleksander Blomskøld für aktuelle Versionen dieser Bibliothek erwähnte Problemumgehung befolgt hatte.
Asgs
Hallo, ich verwende Eclipse und kann es nicht zum Laufen bringen. ClassIndex.getPackageClasses ("my.package") gibt eine leere Karte zurück
Juan
11

Der robusteste Mechanismus zum Auflisten aller Klassen in einem bestimmten Paket ist derzeit ClassGraph , da er ein möglichst breites Spektrum von Klassenpfadspezifikationsmechanismen verarbeitet , einschließlich des neuen JPMS-Modulsystems. (Ich bin der Autor.)

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

Hier ist, wie ich es mache. Ich scanne alle Unterordner (Unterpakete) und versuche nicht, anonyme Klassen zu laden:

   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);             
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  
Nadav B.
quelle
4

Ich habe ein einfaches Github-Projekt zusammengestellt, das dieses Problem löst:

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

Es sollte sowohl für dateibasierte Klassenpfade als auch für JAR-Dateien funktionieren.

Wenn Sie nach dem Auschecken des Projekts 'make' ausführen, wird Folgendes ausgedruckt:

 Cleaning...
rm -rf build/
 Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
 Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ . 
jar cf build/ClassEnumerator.jar -C build/classes/ pro
 Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg'  =>  class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar  test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/'  =>  class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class'  =>  class 'null'
ClassDiscovery: JarEntry 'test/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Tests Passed. 

Siehe auch meine andere Antwort

Dave Dopson
quelle
4

Ja, mit wenigen APIs, die Sie können, hier ist, wie ich es gerne mache, mit diesem Problem konfrontiert, das ich im Ruhezustand verwendete und Klassen finden musste, die mit einer bestimmten Anmerkung versehen waren.

Machen Sie diese zu einer benutzerdefinierten Anmerkung, mit der Sie markieren, welche Klassen Sie abholen möchten.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {

}

Dann markiere deine Klasse damit wie

@EntityToBeScanned 
public MyClass{

}

Erstellen Sie diese Dienstprogrammklasse mit der folgenden Methode

public class ClassScanner {

    public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
        Reflections reflections = new Reflections(".*");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
        return annotated;
    }

}

Rufen Sie die Methode allFoundClassesAnnotatedWithEntityToBeScanned () auf, um einen Satz gefundener Klassen abzurufen .

Sie benötigen die unten angegebenen Bibliotheken

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>
Sujal Mandal
quelle
3

Sie müssen jeden Klassenladeprogrammeintrag im Klassenpfad nachschlagen:

    String pkg = "org/apache/commons/lang";
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL[] urls = ((URLClassLoader) cl).getURLs();
    for (URL url : urls) {
        System.out.println(url.getFile());
        File jar = new File(url.getFile());
        // ....
    }   

Wenn der Eintrag ein Verzeichnis ist, suchen Sie einfach im rechten Unterverzeichnis nach:

if (jar.isDirectory()) {
    File subdir = new File(jar, pkg);
    if (!subdir.exists())
        continue;
    File[] files = subdir.listFiles();
    for (File file : files) {
        if (!file.isFile())
            continue;
        if (file.getName().endsWith(".class"))
            System.out.println("Found class: "
                    + file.getName().substring(0,
                            file.getName().length() - 6));
    }
}   

Wenn der Eintrag die Datei und die JAR-Datei ist, überprüfen Sie die ZIP-Einträge:

else {
    // try to open as ZIP
    try {
        ZipFile zip = new ZipFile(jar);
        for (Enumeration<? extends ZipEntry> entries = zip
                .entries(); entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (!name.startsWith(pkg))
                continue;
            name = name.substring(pkg.length() + 1);
            if (name.indexOf('/') < 0 && name.endsWith(".class"))
                System.out.println("Found class: "
                        + name.substring(0, name.length() - 6));
        }
    } catch (ZipException e) {
        System.out.println("Not a ZIP: " + e.getMessage());
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

Sobald Sie alle Klassennamen im Paket haben, können Sie versuchen, sie mit Reflection zu laden und zu analysieren, ob es sich um Klassen oder Schnittstellen usw. handelt.

Donau-Seemann
quelle
Was würden Sie für ein Paket in eine Jar-Datei eingeben?
Kyle Bridenstine
In diesem Beispiel werden keine Unterpakete durchlaufen. Vielleicht ist das für einige von Interesse ... @ mr-tea Geben Sie einfach das Paket an, nach dem Sie suchen. Ich habe dies in ein Projekt eingefügt, ein Testpaket innerhalb dieses Projekts angegeben, es kompiliert und verpackt und das Beispiel aus der Hauptmethode der JAR aufgerufen. Lief wie am Schnürchen. :)
Christian
3

Ich habe versucht, die Reflections-Bibliothek zu verwenden, hatte jedoch einige Probleme bei der Verwendung, und es gab zu viele Jars, die ich einschließen sollte, um einfach die Klassen in einem Paket zu erhalten.

Ich werde eine Lösung veröffentlichen, die ich in dieser doppelten Frage gefunden habe: Wie erhalte ich alle Klassennamen in einem Paket?

Die Antwort wurde von sp00m geschrieben ; Ich habe einige Korrekturen hinzugefügt, damit es funktioniert:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

public final class ClassFinder {

    private final static char DOT = '.';
    private final static char SLASH = '/';
    private final static String CLASS_SUFFIX = ".class";
    private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";

    public final static List<Class<?>> find(final String scannedPackage) {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String scannedPath = scannedPackage.replace(DOT, SLASH);
        final Enumeration<URL> resources;
        try {
            resources = classLoader.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
        }
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        while (resources.hasMoreElements()) {
            final File file = new File(resources.nextElement().getFile());
            classes.addAll(find(file, scannedPackage));
        }
        return classes;
    }

    private final static List<Class<?>> find(final File file, final String scannedPackage) {
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        if (file.isDirectory()) {
            for (File nestedFile : file.listFiles()) {
                classes.addAll(find(nestedFile, scannedPackage));
            }
        //File names with the $1, $2 holds the anonymous inner classes, we are not interested on them. 
        } else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {

            final int beginIndex = 0;
            final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
            final String className = file.getName().substring(beginIndex, endIndex);
            try {
                final String resource = scannedPackage + DOT + className;
                classes.add(Class.forName(resource));
            } catch (ClassNotFoundException ignore) {
            }
        }
        return classes;
    }

}

Um es zu verwenden, rufen Sie einfach die in diesem Beispiel erwähnte find-Methode als sp00n auf: Ich habe bei Bedarf die Erstellung von Instanzen der Klassen hinzugefügt.

List<Class<?>> classes = ClassFinder.find("com.package");

ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
    Constructor constructor = aClass.getConstructor();
    //Create an object of the class type
    constructor.newInstance();
    //...
}
Martín C.
quelle
3

Ich habe gerade eine util-Klasse geschrieben, die Testmethoden enthält. Sie können eine Überprüfung durchführen lassen ~

IteratePackageUtil.java:

package eric.j2se.reflect;

import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * an util to iterate class in a package,
 * 
 * @author eric
 * @date Dec 10, 2013 12:36:46 AM
 */
public class IteratePackageUtil {
    /**
     * <p>
     * Get set of all class in a specified package recursively. this only support lib
     * </p>
     * <p>
     * class of sub package will be included, inner class will be included,
     * </p>
     * <p>
     * could load class that use the same classloader of current class, can't load system packages,
     * </p>
     * 
     * @param pkg
     *            path of a package
     * @return
     */
    public static Set<Class<? extends Object>> getClazzSet(String pkg) {
        // prepare reflection, include direct subclass of Object.class
        Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
                .filterInputsBy(new FilterBuilder().includePackage(pkg)));

        return reflections.getSubTypesOf(Object.class);
    }

    public static void test() {
        String pkg = "org.apache.tomcat.util";

        Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
        for (Class<? extends Object> clazz : clazzSet) {
            System.out.println(clazz.getName());
        }
    }

    public static void main(String[] args) {
        test();
    }
}
Eric Wang
quelle
3

Die Lösung von Aleksander Blomskøld hat bei parametrisierten Tests @RunWith(Parameterized.class)mit Maven bei mir nicht funktioniert . Die Tests wurden korrekt benannt und auch dort gefunden, aber nicht ausgeführt:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec

Ein ähnliches Problem wurde berichtet hier .

In meinem Fall @Parameters Instanzen jeder Klasse in einem Paket erstellt. Die Tests haben gut funktioniert, wenn sie lokal in der IDE ausgeführt wurden. Beim Ausführen von Maven wurden jedoch keine Klassen mit Aleksander Blomskølds Lösung gefunden.

Ich habe es mit dem folgenden Ausschnitt zum Laufen gebracht, der von David Pärssons Kommentar zu Aleksander Blomskølds Antwort inspiriert wurde:

Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
            .addUrls(ClasspathHelper.forJavaClassPath()) 
            .filterInputsBy(new FilterBuilder()
            .include(FilterBuilder.prefix(basePackage))));

Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);
Thorsten
quelle
3

Was ist 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 dann die Funktion ü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 keinen Import-Helfer 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 klappt:

  • von Ihrer IDE

  • für eine JAR-Datei

  • ohne externe Abhängigkeiten

tirz
quelle
2

Fast alle Antworten verwenden Reflectionsoder lesen Klassendateien aus dem Dateisystem. Wenn Sie versuchen, Klassen aus dem Dateisystem zu lesen, können Fehler auftreten, wenn Sie Ihre Anwendung als JAR oder eine andere verpacken. Möglicherweise möchten Sie zu diesem Zweck auch keine separate Bibliothek verwenden.

Hier ist ein anderer Ansatz, der reines Java ist und nicht vom Dateisystem abhängt.

import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class PackageUtil {

    public static Collection<Class> getClasses(final String pack) throws Exception {
        final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
        return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
                .map(javaFileObject -> {
                    try {
                        final String[] split = javaFileObject.getName()
                                .replace(".class", "")
                                .replace(")", "")
                                .split(Pattern.quote(File.separator));

                        final String fullClassName = pack + "." + split[split.length - 1];
                        return Class.forName(fullClassName);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }

                })
                .collect(Collectors.toCollection(ArrayList::new));
    }
}

Java 8 ist kein Muss . Sie können for-Schleifen anstelle von Streams verwenden. Und Sie können es so testen

public static void main(String[] args) throws Exception {
    final String pack = "java.nio.file"; // Or any other package
    PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}
bhdrkn
quelle
1
Dies ist aus folgenden Gründen nicht sehr nützlich: JDK muss verwendet ToolProvider.getSystemJavaCompiler()werden. Dieser Code scannt keine verschachtelten Pakete.
v.ladynev
Ich kann es nicht schaffen, mit einem Paket eines externen
Glases zu arbeiten
1

Vorausgesetzt, Sie verwenden keine dynamischen Klassenlader, können Sie den Klassenpfad durchsuchen und für jeden Eintrag das Verzeichnis oder die JAR-Datei durchsuchen.

Lawrence Dol
quelle
1

Erwähnenswert

Wenn Sie eine Liste aller Klassen unter einem Paket haben möchten, können Sie Reflectionfolgendermaßen vorgehen:

List<Class> myTypes = new ArrayList<>();

Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
    myTypes.add(Class.forName(s));
}

Dadurch wird eine Liste von Klassen erstellt, die Sie später nach Belieben verwenden können.

Maroun
quelle
1

Es ist sehr gut möglich, aber ohne zusätzliche Bibliotheken Reflectionsist es schwierig ...
Es ist schwierig, weil Sie nicht das vollständige Instrument haben, um den Klassennamen zu erhalten.
Und ich nehme den Code meiner ClassFinderKlasse:

package play.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by LINKOR on 26.05.2017 in 15:12.
 * Date: 2017.05.26
 */
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
    try {
        file = new JarFile(filePath);
    } catch (IOException e) {
        trouble = true;
    }
}

public List<String> findClasses(String pkg) {
    ArrayList<String> classes = new ArrayList<>();
    Enumeration<JarEntry> entries = file.entries();
    while (entries.hasMoreElements()) {
        JarEntry cls = entries.nextElement();
        if (!cls.isDirectory()) {
            String fileName = cls.getName();
            String className = fileName.replaceAll("/",         ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
            if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
        }
    }
    return classes;
}
}
Muskovets
quelle
0

Basierend auf der Antwort von @ Staale und in dem Versuch, mich nicht auf Bibliotheken von Drittanbietern zu verlassen, würde ich den Dateisystemansatz implementieren, indem ich den physischen Speicherort des ersten Pakets mit folgenden Informationen untersuche :

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();

new java.io.File(
    klass.getResource(
        "/" + curPackage.replace( "." , "/")
    ).getFile()
).listFiles(
    new java.io.FileFilter() {
        public boolean accept(java.io.File file) {
            final String classExtension = ".class";

            if ( file.isFile()
                && file.getName().endsWith(classExtension)
                // avoid inner classes
                && ! file.getName().contains("$") )
            {
                try {
                    String className = file.getName();
                    className = className.substring(0, className.length() - classExtension.length());
                    foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(System.out);
                }
            }

            return false;
        }
    }
);

foundClasses = foundClassesDyn.toArray(foundClasses);
Gemeinschaft
quelle
0

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

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

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

Wenn Sie jedoch alle in einem Paket haben möchten, lassen Sie einfach alle Klassen in diesem Paket eine bestimmte Schnittstelle implementieren.

Rodney P. Barbati
quelle
0

einfaches Java: FindAllClassesUsingPlainJavaReflectionTest.java

@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {

  private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
    log.error(throwable.getLocalizedMessage());
    return new RuntimeException(throwable);
  };

  private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {

    Locale locale = Locale.getDefault();
    Charset charset = StandardCharsets.UTF_8;
    val fileManager = ToolProvider.getSystemJavaCompiler()
                                  .getStandardFileManager(/* diagnosticListener */ null, locale, charset);

    StandardLocation location = StandardLocation.CLASS_PATH;
    JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
    Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
    val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
                             .getOrElseThrow(asRuntimeException);

    String pathToPackageAndClass = basePackageName.replace(".", File.separator);
    Function<String, String> mapToClassName = s -> {
      String prefix = Arrays.stream(s.split(pathToPackageAndClass))
                            .findFirst()
                            .orElse("");
      return s.replaceFirst(prefix, "")
              .replaceAll(File.separator, ".");
    };

    return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
                        .filter(javaFileObject -> javaFileObject.getKind().equals(kind))
                        .map(FileObject::getName)
                        .map(fileObjectName -> fileObjectName.replace(".class", ""))
                        .map(mapToClassName)
                        .map(className -> Try.of(() -> Class.forName(className))
                                             .getOrElseThrow(asRuntimeException))
                        .collect(Collectors.toList());
  };

  @Test
  @DisplayName("should get classes recursively in given package")
  void test() {
    Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
    assertThat(classes).hasSizeGreaterThan(4);
    classes.stream().map(String::valueOf).forEach(log::info);
  }
}

PS: Um Boilerplates für die Behandlung von Fehlern usw. zu vereinfachen, verwende ich hier vavrund lombokBibliotheken

Andere Implementierungen finden Sie in meinem GitHub-Dolch für Dolchok / Java-Reflection-Find-Annotated-Classes-or-Methods

Maksim Kostromin
quelle
0

Ich konnte keine kurze Arbeit für etwas so Einfaches finden. Also hier ist es, ich habe es selbst gemacht, nachdem ich eine Weile herumgeschraubt habe:

    Reflections reflections =
        new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                .setUrls(ClasspathHelper.forPackage(packagePath))
                .setScanners(new SubTypesScanner(false)));

    Set<String> typeList = reflections.getAllTypes(); 
botenvouwer
quelle
0

Wenn Sie im Frühlingsland sind, können Sie verwenden PathMatchingResourcePatternResolver;

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");

    Arrays.asList(resources).forEach(r->{
        ...
    });
Schwarz
quelle
-4

Dies ist nicht möglich, da möglicherweise nicht alle Klassen im Paket geladen werden, während Sie immer das Paket einer Klasse kennen.

Marko
quelle