wake-up-neo.com

Können Sie alle Klassen in einem Paket mit Reflektion finden?

Ist es möglich, alle Klassen oder Schnittstellen in einem bestimmten Paket zu finden? (Bei einem schnellen Blick auf beispielsweise Package würde es wie nein erscheinen.)

468
Jonik

Dies ist aufgrund der dynamischen Eigenschaften von Klassenladern nicht möglich. Klassenladeprogramme müssen den VM nicht mitteilen, welche Klassen sie bereitstellen können. Stattdessen handelt es sich lediglich um Anforderungen für Klassen, die eine Klasse zurückgeben und eine Klasse zurückgeben oder eine Ausnahme auslösen müssen.

Wenn Sie jedoch eigene Klassenladeprogramme schreiben oder die Klassenpfade und die zugehörigen Glieder untersuchen, können Sie diese Informationen finden. Dies erfolgt jedoch über Dateisystemvorgänge und nicht über die Spiegelung. Es gibt sogar Bibliotheken, die Ihnen dabei helfen können.

Wenn es Klassen gibt, die generiert oder aus der Ferne bereitgestellt werden, können Sie diese Klassen nicht erkennen.

Die normale Methode ist stattdessen, die Klassen, auf die Sie zugreifen möchten, irgendwo in einer Datei zu registrieren oder sie in einer anderen Klasse zu referenzieren. Oder verwenden Sie einfach Konventionen, wenn es um die Benennung geht.

Nachtrag: Die Reflections Library ermöglicht das Nachschlagen von Klassen im aktuellen Klassenpfad. Es kann verwendet werden, um alle Klassen in einem Paket abzurufen:

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

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);
338
Staale

Sie sollten sich wahrscheinlich die Open Source Reflections-Bibliothek ansehen. Mit ihm können Sie leicht erreichen, was Sie wollen.

Richten Sie zunächst den Reflexionsindex ein (es ist etwas unordentlich, 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"))));

Dann können Sie alle Objekte in einem bestimmten Paket abfragen:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
170

Google Guava 14 enthält eine neue Klasse ClassPathNAME _ mit drei Methoden zum Suchen nach Klassen der obersten Ebene:

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

Weitere Informationen finden Sie in den ClassPathjavadocs .

111

Sie könnten diese Methode verwenden1 das verwendet die 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 aus http://snippets.dzone.com/posts/show/4831 übernommen, die archiviert wurde vom Internetarchiv, wie jetzt verlinkt. Das Snippet ist auch unter https://dzone.com/articles/get-all-classes-within-package verfügbar.

96
user59634

Frühling

Dieses Beispiel ist 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 immer noch als @Beta markiert. Passen Sie daher im Produktionscode auf.

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
  }
}
82
voho

Hallo. Ich hatte immer Probleme mit den oben genannten Lösungen (und auf anderen Websites).
Als Entwickler programmiere ich ein Addon für eine API. Die API erlaubt die Verwendung von externen Bibliotheken oder Tools von Drittanbietern. Das Setup besteht auch aus einer Mischung von 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 langer Recherche 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 funktionieren wird.

Der Code:

Dieser Code durchsucht ein gegebenes 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;
}

Mit diesen drei Methoden können Sie alle Klassen in einem bestimmten Paket finden.
Sie verwenden es so:

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

Die Erklärung:

Die Methode erhält zuerst das aktuelle ClassLoader. Es ruft dann alle Ressourcen ab, die das Paket enthalten, und durchläuft diese URLs. Dann wird eine URLConnection erstellt und festgelegt, welche Art von URL wir haben. Dies kann entweder ein Verzeichnis (FileURLConnection) oder ein Verzeichnis in einer JAR- oder Zip-Datei (JarURLConnection) sein. Abhängig von der Art der Verbindung werden zwei verschiedene Methoden aufgerufen.

Zuerst sehen wir, was passiert, wenn es sich um ein _ FileURLConnection handelt.
Er prüft zuerst, ob die übergebene Datei existiert und ein Verzeichnis ist. Wenn dies der Fall ist, wird geprüft, ob es sich um eine Klassendatei handelt. In diesem Fall wird ein Class-Objekt erstellt und in das ArrayList-Objekt eingefügt. Wenn es sich nicht um eine Klassendatei handelt, sondern um ein Verzeichnis, führen wir einfach eine Iteration durch und machen dasselbe. Alle anderen Fälle/Dateien werden ignoriert.

Wenn URLConnection ein JarURLConnection ist, wird die 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 Class-Objekt erstellt und im ArrayList gespeichert.

Nachdem alle Ressourcen analysiert wurden, gibt sie (die Hauptmethode) den ArrayList-Container aller Klassen in dem angegebenen Paket zurück, den der aktuelle ClassLoader kennt.

Wenn der Prozess an irgendeinem Punkt fehlschlägt, wird ein ClassNotFoundException mit detaillierten Informationen zur genauen Ursache geworfen.

31
BrainStone

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;
    }
}
12
Williams López

Im Allgemeinen erlauben Klassenladeprogramme nicht das Durchsuchen aller Klassen im Klassenpfad. Normalerweise wird jedoch nur der Klassenlader UrlClassLoader verwendet, mit dem wir die Liste der Verzeichnisse und JAR-Dateien abrufen können (siehe getURLs ) und sie einzeln öffnen, um verfügbare Klassen aufzulisten. Dieser Ansatz, der als Klassenpfad-Scanning bezeichnet wird, ist in Scannotation und Reflections implementiert.

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 Anmerkungsprozessor zu schreiben, der alle mit Anmerkungen versehenen Klassen zur Kompilierzeit sammelt und die Indexdatei für die Laufzeit erstellt. Dieser Mechanismus ist in ClassIndex library implementiert:

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

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

Beachten Sie, dass keine zusätzlichen Einstellungen erforderlich sind, da der Scanvorgang vollständig automatisiert ist, da der Java-Compiler alle im Klassenpfad gefundenen Prozessoren automatisch erkennt.

12
Sławek

Ich habe FastClasspathScanner geschrieben, um dieses Problem zu lösen. Es verarbeitet viele verschiedene Arten von Klassenpfad-Scan-Aufgaben, hat eine einfache API, arbeitet mit vielen verschiedenen ClassLoadern und Klassenpfad-Umgebungen zusammen, wurde sorgfältig parallelisiert und ist für hohe Geschwindigkeit und geringen Speicherbedarf optimiert. Es kann sogar eine GraphViz-Visualisierung des Klassendiagramms generiert werden, die zeigt, wie Klassen miteinander verbunden sind.

Für Ihre ursprüngliche Frage nach dem Finden aller Klassen oder Schnittstellen in einem bestimmten Paket können Sie Folgendes tun:

List<String> classNames = new FastClasspathScanner("com.mypackage").scan()
    .getNamesOfAllClasses();

Es gibt viele mögliche Varianten dazu - vollständige Informationen finden Sie in der Dokumentation (oben verlinkt).

6
Luke Hutchison

So mache ich es. 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;
  }  
5
Nadav B

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

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

Es sollte für BEIDE dateibasierte Klassenpfade UND für JAR-Dateien funktionieren.

Wenn Sie nach dem Auschecken des Projekts "make" ausführen, wird Folgendes ausgegeben:

 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

4
Dave Dopson

Ja, mit ein paar APIs kannst du, hier ist, wie ich es gerne mache, vor diesem Problem stand, das ich im Hibernate-Core verwendete und Klassen finden musste, die mit einer bestimmten Anmerkung versehen wurden.

Machen Sie diese zu einer benutzerdefinierten Anmerkung, mit der Sie die Klassen markieren, die 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 markieren Sie Ihre Klasse damit

@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 allFoundClassesAnnotatedWithEntityToBeScanned () - Methode auf, um ein Set der gefundenen Klassen zu erhalten.

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>
4
Sujal Mandal

Ich habe versucht, die Reflections-Bibliothek zu verwenden, hatte jedoch einige Probleme bei der Verwendung, und es gab zu viele Gläser, die ich verwenden sollte, um einfach die Klassen für ein Paket zu erhalten.

Ich werde eine Lösung posten, die ich in dieser doppelten Frage gefunden habe: Wie bekomme 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 benutzen, rufen Sie einfach die find-Methode als sp00n auf, die in diesem Beispiel erwähnt wird: 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();
    //...
}
3
Martín C

Sie müssen jeden Klassenladeeintrag 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 es sich bei dem Eintrag um die Datei und um jar handelt, überprüfen Sie die Zip-Einträge davon: 

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());
    }
}

Wenn Sie nun alle Klassennamen im Paket haben, können Sie sie mit Reflektion laden und analysieren, ob es sich um Klassen oder Schnittstellen handelt.

3
Danubian Sailor

Fast alle Antworten verwenden entweder Reflections oder lesen Klassendateien aus dem Dateisystem. Wenn Sie versuchen, Klassen aus dem Dateisystem zu lesen, erhalten Sie möglicherweise Fehler, wenn Sie Ihre Anwendung als JAR oder eine andere packen. Möglicherweise möchten Sie auch keine separate Bibliothek für diesen Zweck verwenden. 

Hier ist ein weiterer 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 anstelle von Streams for-Schleifen verwenden. Sie können es auch 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);
}
2
bhdrkn

Erwähnenswert

Wenn Sie eine Liste aller Klassen in einem Paket haben möchten, können Sie Reflection folgendermaßen verwenden:

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.

2
Maroun

Aleksander Blomskølds Lösung funktionierte bei parametrisierten Tests @RunWith(Parameterized.class) nicht, wenn Maven verwendet wurde Die Tests wurden korrekt benannt und auch 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 gemeldet hier .

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

Ich habe es mit dem folgenden Ausschnitt gemacht, 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);
2
Thorsten

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

1
Lawrence Dol

Ich habe gerade eine util-Klasse geschrieben, sie enthält Testmethoden, Sie können einen Check ~ haben

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();
    }
}
1
Eric Wang

Es ist sehr gut möglich, aber ohne zusätzliche Bibliotheken wie Reflections ist es hart ...
Es ist schwierig, weil Sie kein vollständiges Instrument für den Klassennamen haben.
Und ich nehme den Code meiner ClassFinder-Klasse:

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;
}
}
1
Muskovets

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 die Boilerplates für die Behandlung von Fehlern usw. zu vereinfachen, verwende ich hier die Bibliotheken vavr und lombok

weitere Implementierungen finden Sie in Mein GitHub-Dolch/Java-Reflection-Find-Annotated-Classes-or-Methods-Repo

0

Basierend auf @ Staales Antwort und um nicht auf Bibliotheken von Drittanbietern angewiesen zu sein, würde ich den Dateisystemansatz implementieren, indem ich den physischen Speicherort des ersten Pakets mit folgendem prüfe:

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);
0
user1026870

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 einfach die gleiche Schnittstelle in allen Klassen, die Sie laden möchten, unabhängig vom Paket, und verwenden Sie Spring, um Ihnen alle Instanzen 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 dagegen alle in einem Paket haben, wollen Sie einfach, dass alle Klassen in diesem Paket eine bestimmte Schnittstelle implementieren.

0