Java 你能用反射找到包中的所有类吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/520328/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:43:47  来源:igfitidea点击:

Can you find all classes in a package using reflection?

javareflectionpackages

提问by Jonik

Is it possible to find all classes or interfaces in a given package? (Quickly looking at e.g. Package, it would seem like no.)

是否可以找到给定包中的所有类或接口?(快速查看 eg Package,似乎没有。)

采纳答案by Staale

Due to the dynamic nature of class loaders, this is not possible. Class loaders are not required to tell the VM which classes it can provide, instead they are just handed requests for classes, and have to return a class or throw an exception.

由于类加载器的动态特性,这是不可能的。类加载器不需要告诉 VM 它可以提供哪些类,相反,它们只是处理类的请求,并且必须返回一个类或抛出异常。

However, if you write your own class loaders, or examine the classpaths and it's jars, it's possible to find this information. This will be via filesystem operations though, and not reflection. There might even be libraries that can help you do this.

但是,如果您编写自己的类加载器,或检查类路径及其 jar,则可以找到此信息。不过,这将通过文件系统操作,而不是反射。甚至可能有一些库可以帮助您做到这一点。

If there are classes that get generated, or delivered remotely, you will not be able to discover those classes.

如果有远程生成或交付的类,您将无法发现这些类。

The normal method is instead to somewhere register the classes you need access to in a file, or reference them in a different class. Or just use convention when it comes to naming.

正常的方法是在某个地方注册您需要在文件中访问的类,或者在不同的类中引用它们。或者只是在命名时使用约定。

Addendum: The Reflections Librarywill allow you to look up classes in the current classpath. It can be used to get all classes in a package:

附录:反射库将允许您在当前类路径中查找类。它可用于获取包中的所有类:

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

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

回答by Marko

It is not possible, since all classes in the package might not be loaded, while you always knows package of a class.

这是不可能的,因为可能不会加载包中的所有类,而您总是知道类的包。

回答by Staale

You could use this method1that uses the ClassLoader.

您可以使用此方法1使用了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;
}

__________
1This method was taken originally from http://snippets.dzone.com/posts/show/4831, which was archivedby the Internet Archive, as linked to now. The snippet is also available at https://dzone.com/articles/get-all-classes-within-package.

__________
1这种方法最初是取自http://snippets.dzone.com/posts/show/4831,这是存档通过互联网档案馆,为链接到现在。该代码段也可从https://dzone.com/articles/get-all-classes-within-package 获得

回答by Lawrence Dol

Provided you are not using any dynamic class loaders you can search the classpath and for each entry search the directory or JAR file.

如果您没有使用任何动态类加载器,您可以搜索类路径并为每个条目搜索目录或 JAR 文件。

回答by Aleksander Blomsk?ld

You should probably take a look at the open source Reflections library. With it you can easily achieve what you want.

您或许应该看看开源的Reflections 库。有了它,您可以轻松实现您想要的。

First, setup the reflections index (it's a bit messy since searching for all classes is disabled by default):

首先,设置反射索引(由于默认禁用搜索所有类,因此有点混乱):

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"))));

Then you can query for all objects in a given package:

然后您可以查询给定包中的所有对象:

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

回答by Dave Dopson

I put together a simple github project that solves this problem:

我整理了一个简单的github项目来解决这个问题:

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

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

It should work for BOTH file-based classpaths AND for jar files.

它应该适用于基于文件的类路径和 jar 文件。

If you run 'make' after checking out the project it will print this out:

如果您在签出项目后运行“make”,它将打印出来:

 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. 

See also my other answer

另见我的另一个答案

回答by Christoph Leiter

Google Guava 14 includes a new class ClassPathwith three methods to scan for top level classes:

Google Guava 14 包含一个新类,ClassPath其中包含三种扫描顶级类的方法:

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

See the ClassPathjavadocsfor more info.

有关更多信息,请参阅ClassPathjavadoc

回答by S?awek

In general class loaders do not allow for scanning through all the classes on the classpath. But usually the only used class loader is UrlClassLoader from which we can retrieve the list of directories and jar files (see getURLs) and open them one by one to list available classes. This approach, called class path scanning, is implemented in Scannotationand Reflections.

一般来说,类加载器不允许扫描类路径上的所有类。但通常唯一使用的类加载器是 UrlClassLoader,我们可以从中检索目录和 jar 文件的列表(请参阅getURLs)并一一打开它们以列出可用的类。这种方法称为类路径扫描,在ScannotationReflections 中实现

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

Another approach is to use Java Pluggable Annotation Processing APIto write annotation processor which will collect all annotated classes at compile time and build the index file for runtime use. This mechanism is implemented in ClassIndexlibrary:

另一种方法是使用Java Pluggable Annotation Processing API编写 annotation processor,它将在编译时收集所有带注释的类并构建索引文件以供运行时使用。该机制在ClassIndex库中实现:

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

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

Notice that no additional setup is needed as the scanning is fully automated thanks to Java compiler automatically discovering any processors found on the classpath.

请注意,由于 Java 编译器会自动发现在类路径上找到的任何处理器,因此无需额外设置,因为扫描是完全自动化的。

回答by Danubian Sailor

You need to look up every class loader entry in the class path:

您需要在类路径中查找每个类加载器条目:

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

If entry is directory, just look up in the right subdirectory:

如果条目是目录,只需在正确的子目录中查找:

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

If the entry is the file, and it's jar, inspect the ZIP entries of it:

如果条目是文件,并且是 jar,请检查它的 ZIP 条目:

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

Now once you have all class names withing package, you can try loading them with reflection and analyze if they are classes or interfaces, etc.

现在,一旦您将所有类名都包含在包中,您就可以尝试使用反射加载它们并分析它们是类还是接口等。

回答by Martín C

I've been trying to use the Reflections library, but had some problems using it, and there were too many jars I should include just to simply obtain the classes on a package.

我一直在尝试使用 Reflections 库,但是在使用它时遇到了一些问题,而且我应该包含太多 jar 只是为了简单地获取包上的类。

I'll post a solution I've found in this duplicate question: How to get all classes names in a package?

我将发布我在这个重复问题中找到的解决方案:如何获取包中的所有类名称?

The answer was written by sp00m; I've added some corrections to make it work:

答案写由sp00m; 我添加了一些更正以使其工作:

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 ,  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;
    }

}

To use it just call the find method as sp00n mentioned in this example: I've added the creation of instances of the classes if needed.

要使用它,只需像本示例中提到的 sp00n 一样调用 find 方法:如果需要,我已经添加了类实例的创建。

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