你能使用反射在一个包中找到所有类吗?

Can you find all classes in a package using reflection?

提问人:Jonik 提问时间:2/6/2009 最后编辑:Willi MentzelJonik 更新时间:3/30/2023 访问量:516558

问:

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

Java 反射

评论

2赞 Kip 2/17/2010
仅供参考,解决方案 Amit 链接到有效,尽管如果类路径中包含空格字符(也可能用于其他非字母数字字符),它有一个错误。如果您在任何类型的生产代码中使用它,请参阅我对他的回答的评论以获取解决方法。
2赞 barfuin 1/13/2014
另请注意这篇文章
1赞 Cfx 5/10/2015
查看相关答案: stackoverflow.com/a/30149061/4102160
1赞 sp00m 12/2/2016
另请注意这篇文章
2赞 Luke Hutchison 10/5/2019
请参阅下面关于 ClassGraph 的回答,它是目前扫描类路径和模块路径的最可靠的方法。

答:

-3赞 Marko 2/6/2009 #1

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

419赞 Staale 2/6/2009 #2

由于类加载器的动态特性,这是不可能的。类加载器不需要告诉 VM 它可以提供哪些类,而只是向类发出请求,并且必须返回类或引发异常。

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

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

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

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

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

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

评论

14赞 Mr. Shiny and New 安宇 2/6/2009
无法查询类名已经困扰了我很长时间。当然,这很难,性能可能会有很大差异,并且对于某些类加载器来说,列表是未定义或无界的,但有一些方法可以解决这个问题。
22赞 Alex Spurling 12/5/2012
请注意,此解决方案将不起作用,因为默认情况下 getSubTypesOf 不返回 Object 的子类型。请参阅 Aleksander Blomskøld 的解决方案,了解如何配置 SubTypeScanner。
20赞 mike jones 6/26/2013
反射需要番石榴。番石榴很大。版本 14.0.1 为 2.1MB。
3赞 Konstantinos Margaritis 10/5/2013
对我不起作用。Mac OSX - Reflections 依赖项版本 0.9.9-RC1 (maven) - JDK 1.7。重新考虑已接受的答案。@AleksanderBlomsk答案是要去的!!!!
79赞 João Rocha da Silva 9/24/2014
如果这返回一个空列表,请像这样初始化 Reflections 对象: Reflections reflections = new Reflections(“your.package.here”, new SubTypesScanner(false));
153赞 user59634 2/6/2009 #3

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

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

评论

9赞 Kip 2/17/2010
如果我的路径包含空格,我就遇到了问题。URL 类将空格转义到 ,但构造函数将其视为文字百分号 2 0。我通过将行更改为以下行来修复它: 这也意味着我必须添加到 throws 子句中%20new File()dirs.add(...)dirs.add(new File(resource.toURI()));URISyntaxExceptiongetClasses
24赞 R.A 10/23/2015
你只是从 dzone.com/articles/get-all-classes-within-package 复制的!下次请参考来源
28赞 sc0p 12/6/2015
+1,因为此解决方案不需要外部库...永远不要,真的永远不要为了实现这样的小事而将你的代码与库随机耦合。您是否知道您正在为攻击者添加潜在的攻击面?2015 年 11 月,发现了一个 Apache Commons 问题,该问题仅通过在 Jboss/Weblogic 上部署的应用程序的类路径中就会导致远程命令执行 [foxglovesecurity.com/2015/11/06/...
1赞 user1523177 2/8/2016
@Qix正确地指出,此代码不支持 jar。为了支持jar和目录。代码已更改如下:
2赞 payne 6/10/2020
我无法让它工作。我们能否得到一个有效的包名输入示例,以及一个示例 projet 结构?
1赞 Lawrence Dol 3/4/2010 #4

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

209赞 Aleksander Blomskøld 3/6/2012 #5

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

首先,设置反射索引(这有点混乱,因为默认情况下禁用搜索所有类):

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

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

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

评论

6赞 mtrc 8/25/2012
啊,我们开始吧:code.google.com/p/reflections/issues/detail?id=122。默认情况下,对象被排除在外,但您可以重新调整它。谢谢你把我引向这个图书馆,太棒了!
1赞 David Pärsson 2/11/2013
我在 Mac 上遇到了使用此代码(与本机库相关)的问题,但使用而不是上述代码为我解决了这些问题。代码也更少!.addUrls(ClasspathHelper.forJavaClassPath())
4赞 JBA 10/17/2014
如果有人想知道获取默认包的最简单方法是将前缀设置为空字符串 -> “”。
2赞 Serge Rogatch 2/17/2015
“Reflections”库有一个棘手的许可证:github.com/ronmamo/reflections/blob/master/COPYING.txt 。诀窍是许可证只允许免费使用许可证本身。因此,要真正使用库(而不是许可证),每个人都必须联系作者并协商使用条款。
3赞 Richo 6/23/2015
Serge,我想你误解了WTFPL:wtfpl.net 我认为WTFPL意味着你可以自由地做任何你想做的事情,不仅仅是许可证,还有代码
5赞 Dave Dopson 10/25/2012 #6

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

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

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

如果你在签出项目后运行'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. 

另请参阅我的其他答案

146赞 Christoph Leiter 11/21/2012 #7

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

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

有关详细信息,请参阅 ClassPath javadocs

评论

1赞 Christian 1/9/2015
正如我在下面的评论中提到的,被标记为 ,所以对某些人来说可能不是一个好主意......ClassPath@Beta
1赞 Maarten Bodewes 8/18/2016
说这在反射不起作用的地方起作用有点奇怪,该解决方案无疑是使用反射(和类加载器)功能实现的。
7赞 Christoph Leiter 8/18/2016
我认为他指的是另一个答案中提到的 Reflections 库。
1赞 gorjanz 11/12/2019
在 Java 11 下工作,如果使用 guava 版本 28.1-jre。
1赞 isopropylcyanide 3/21/2020
即使在 8 年后,这个班级仍然不稳定,并被标记为@Beta。我想知道是什么给了。
14赞 Sławek 12/22/2012 #8

通常,类装入器不允许扫描类路径上的所有类。但通常唯一使用的类加载器是 UrlClassLoader,我们可以从中检索目录和 jar 文件的列表(参见 getURL),并逐个打开它们以列出可用的类。这种方法称为类路径扫描,在 Scannotation and Reflections 中实现。

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

另一种方法是使用 Java Pluggable Annotation Processing API 来编写注解处理器,该处理器将在编译时收集所有注解类并构建索引文件以供运行时使用。此机制在 ClassIndex 库中实现:

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

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

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

评论

0赞 asgs 6/18/2013
这是否能发现打包在 Jar 中的类?它似乎对我不起作用。
0赞 Sławek 6/18/2013
您尝试使用哪种工具?
0赞 asgs 6/18/2013
我正在使用 Reflections 库。但是在遵循 @Aleksander Blomskøld 提到的针对此库的最新版本的解决方法后,我让它正常工作。
0赞 Juan 2/27/2019
嗨,我正在使用 eclipse 并且无法让它工作,ClassIndex.getPackageClasses(“my.package”) 返回一个空映射
3赞 Danubian Sailor 9/25/2013 #9

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

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

如果条目是文件,并且是 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());
    }
}

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

评论

0赞 Kyle Bridenstine 7/23/2014
你会为 Jar 文件中的包输入什么?
0赞 Christian 1/9/2015
此示例不会通过子包。也许有些人对此感兴趣...... @mr茶 只需指定您要查找的套餐即可。我把它放在一个项目中,在该项目中指定了一个测试包,对其进行编译和打包,并将示例从 JAR 的 main 方法中调用。像魅力一样工作。:)
3赞 Martín C 12/4/2013 #10

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

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

答案是由 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 $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;
    }

}

若要使用它,只需调用此示例中提到的 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();
    //...
}
3赞 Eric 12/10/2013 #11

我刚写了一个util类,里面有测试方法,可以有查一下~

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();
    }
}
127赞 voho 1/29/2014 #12

春天

此示例适用于 Spring 4,但您也可以在早期版本中找到类路径扫描程序。

// 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 ...
}

谷歌番石榴

注意:在版本 14 中,API 仍标记为@Beta,因此在生产代码中要小心。

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

评论

8赞 JeanValjean 6/21/2014
很好的答案。这里有太多冗长、未经测试、不起作用的解决方案!这个太棒了:它简洁且经过测试(它来自番石榴)。非常好!它很有用,它应该得到更多的赞成票。
1赞 v.ladynev 2/16/2016
要使用番石榴解决方案查找嵌套的静态类,可以使用方法。getAllClasses()
1赞 btpka3 9/5/2017
Spring 示例不适合查找非具体类,例如 anntation( ),因为 ClassPathScanningCandidateComponentProvider#isCandidateComponent@RabbitListener(...) public @interface XxxListener
1赞 Luke 1/23/2018
Spring 解决方案是唯一从可执行 jar 运行时有效的解决方案。
1赞 Abhijit Sarkar 7/20/2019
ClassPathScanningCandidateComponentProvider有效,但它实际上是为春豆准备的。
20赞 Williams López 2/18/2014 #13

在不使用任何额外库的情况下:

package test;

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

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

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

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

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

评论

1赞 Christian 1/9/2015
当我在 JAR 中运行它时,是...... :(upackagenull
1赞 James Jithin 3/17/2016
对于包“com.mycompany.beans”,请将“test”替换为“com/mycompany/beans”
6赞 Alao 11/24/2016
使用此代码时我得到一个空值。似乎只有在您的 jar 是可执行文件时才有效
2赞 user2682877 5/14/2019
如果从 获取包名,则必须添加String pack = getPackage().getName();pack = pack.replaceAll("[.]", "/");
1赞 Holger 11/14/2023
撇开不能保证你可以以这种方式读取目录(或类似目录的资源)不谈,有什么理由写而不是直接的?此外,自 Java 1.1 以来,不推荐使用 of 方法,并且从该版本开始就存在正确的替代>。这意味着,即使在写这个答案的时候,它已经被弃用了 17 年。(InputStream) upackage.getContent()upackage.openStream()readLineDataInputStreamBufferedReaderInputStreamReader
42赞 BrainStone 3/18/2014 #14

你好。我总是对上述解决方案(以及其他网站)有一些问题。
作为一名开发人员,我正在为 API 编写一个插件。该 API 阻止使用任何外部库或第三方工具。该设置还包含 jar 或 zip 文件中的代码和直接位于某些目录中的类文件的混合。因此,我的代码必须能够适应每个设置。经过大量研究,我想出了一种方法,该方法至少适用于所有可能设置的 95%。

下面的代码基本上是永远有效的矫枉过正的方法。

代码:

此代码扫描给定包中包含的所有类。它仅适用于当前 .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;
}

这三种方法使您能够在给定的包中查找所有类。
你像这样使用它:

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

解释:

该方法首先获取当前 .然后,它获取包含所述包的所有资源并迭代这些包。然后,它创建一个并确定我们拥有的 URl 类型。它可以是目录 (),也可以是 jar 或 zip 文件 () 中的目录。根据我们拥有的连接类型,将调用两种不同的方法。ClassLoaderURLURLConnectionFileURLConnectionJarURLConnection

首先,让我们看看如果它是 .
它首先检查传递的文件是否存在并且是否为目录。如果是这种情况,它会检查它是否是类文件。如果是这样,将创建一个对象并将其放入 .如果它不是类文件而是目录,我们只需迭代它并执行相同的操作。所有其他案例/文件将被忽略。
FileURLConnectionClassArrayList

如果 是 ,则将调用其他私人帮助程序方法。此方法遍历 zip/jar 存档中的所有条目。如果一个条目是类文件,并且位于包内,则将创建一个对象并将其存储在 .URLConnectionJarURLConnectionClassArrayList

解析完所有资源后,它(main 方法)返回当前知道的给定包中的所有类。ArrayListClassLoader

如果该过程在任何时候失败,将抛出包含有关确切原因的详细信息。ClassNotFoundException

评论

4赞 Christian 3/30/2015
此示例似乎需要导入 ,这会在编译时生成警告(“warning: sun.net.www.protocol.file.FileURLConnection is Sun proprietary API and may be removed in a future release”)。是否有使用该类的替代方法,或者可以使用注释抑制警告?sun.net.www.protocol.file.FileURLConnection
0赞 coderforlife 5/18/2016
此方法不适用于 bootstrap 类,例如 java.lang、java.util 中的类......可以通过获取 System.getProperty(“sun.boot.class.path”) 来找到这些,用 : 或 ;(取决于操作系统),然后运行上述 checkDirectory 和 checkJarFile 的略微修改版本。
1赞 William Deans 1/6/2017
您可以使用 connection.getClass().getCanonicalName().equals( “sun.net.www.protocol.file.FileURLConnection” ) 来规避警告/错误。如果您真的需要,可以创建一个您认为应该使用 sun.net.www.protocol.file.FileURLConnection 的 URLConnection,还可以将连接类的名称与您创建的类的名称进行比较。如果它们都相同,则可以将其视为 sun.net.www.protocol.file.FileURLConnection 的实例,而不是在类名更改时失败。
1赞 Zardoz89 5/26/2017
@Christian 您可以避免 FileURLConnection 执行类似操作: 这是我在代码上所做的,用于搜索 JPA 注释类if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
0赞 GuiRitter 5/15/2023
@Christian我过去成功地使用了它,但现在,在 2023 年,它不再有效。
3赞 bhdrkn 9/29/2015 #15

几乎所有的答案都使用或从文件系统中读取类文件。如果尝试从文件系统中读取类,则在将应用程序打包为 JAR 或其他应用程序时可能会出现错误。此外,您可能不希望为此目的使用单独的库。Reflections

这是另一种纯 java 方法,不依赖于文件系统。

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 不是必须的。您可以使用 for 循环而不是流。 你可以像这样测试它

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

评论

1赞 v.ladynev 2/17/2016
它不是很有用,因为:需要有JDK才能使用,这段代码不会扫描嵌套的包。ToolProvider.getSystemJavaCompiler()
0赞 Enrico Giurin 3/17/2019
我无法让它与外部 jar 的包一起工作
1赞 Maroun 12/15/2015 #16

值得一提的是

如果你想在某个包下有一个所有类的列表,你可以使用以下方式:Reflection

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

这将创建一个类列表,稍后您可以根据需要使用它们。

27赞 Luke Hutchison 7/31/2016 #17

目前,列出给定包中所有类的最健壮的机制是 ClassGraph,因为它可以处理尽可能广泛的类路径规范机制阵列,包括新的 JPMS 模块系统。(我是作者。

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

评论

0赞 Paul W 7/18/2022
很棒的图书馆!但是,请注意,它不适用于 Android 项目的运行时。不过,有一种方法可以在构建时通过 Gradle 使用它。
1赞 Luke Hutchison 7/19/2022
@PaulW正确的,并且没有计划支持 Dalvik 运行时的 dex/odex 格式,因此构建时扫描是唯一的选择。如果您有任何更改或改进文档,请提出建议。github.com/classgraph/classgraph/wiki/Build-Time-Scanning
0赞 Jonathan S. Fisher 12/11/2022
这适用于古老的 onejar-maven-plugin 吗?
1赞 Luke Hutchison 12/12/2022
@JonathanS.Fisher 它应该可以,我知道它适用于阴影罐,尽管在这种情况下包名称可能会更改。
6赞 Nathan B 8/18/2016 #18

这是我的做法。我扫描了所有子文件夹(子包),并且不尝试加载匿名类:

   /**
   * 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赞 Sujal Mandal 2/8/2017 #19

是的,你可以使用很少的API,这就是我喜欢的做法,面对这个问题,我正在使用休眠核心,并且必须找到用某种注释进行注释的类。

将这些作为自定义注释,您将使用它来标记要选取的类。

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 {

}

然后用它标记你的班级,比如

@EntityToBeScanned 
public MyClass{

}

使这个实用程序类具有以下方法

public class ClassScanner {

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

}

调用 allFoundClassesAnnotatedWithEntityToBeScanned() 方法以获取找到的一组类。

您将需要下面给出的库

<!-- 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>
1赞 Muskovets 5/26/2017 #20

这是很有可能的,但是如果没有像这样的额外库,这很难......
这很难,因为你没有完整的工具来获取类名。
而且,我采用我的类代码:
ReflectionsClassFinder

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;
}
}
3赞 Thorsten 6/29/2017 #21

Aleksander Blomskøld 的解决方案在使用 Maven 时不适用于参数化测试。测试的名称正确,以及找到但未执行的地方:@RunWith(Parameterized.class)

-------------------------------------------------------
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

此处报告了类似的问题。

就我而言,是在包中创建每个类的实例。在 IDE 中本地运行时,测试运行良好。但是,在运行 Maven 时,没有发现 Aleksander Blomskøld 解决方案的类。@Parameters

我确实用以下片段让它工作,这是受 David Pärsson 对 Aleksander Blomskøld 答案的评论的启发:

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);
-1赞 Rodney P. Barbati 10/5/2017 #22

如果您只是想加载一组相关的类,那么 Spring 可以为您提供帮助。

Spring 可以在一行代码中实例化所有实现给定接口的类的列表或映射。列表或映射将包含实现该接口的所有类的实例。

话虽如此,作为从文件系统中加载类列表的替代方法,只需在要加载的所有类中实现相同的接口,而不管包如何,并使用 Spring 为您提供所有这些类的实例。这样,您可以加载(和实例化)所需的所有类,无论它们在哪个包中。

另一方面,如果将它们全部放在一个包中是你想要的,那么只需让该包中的所有类实现一个给定的接口。

请注意,接口本身不必声明任何方法 - 它可以是完全空的。

要注入实现给定接口的类列表,请使用以下代码行...

  @Autowired
  private List<ISomeInterface> implementationList;

也可以使用 Spring 注入类的 Map。如果有兴趣,请阅读文档,了解如何操作。

最后,我将提供另一种解决方案,它比搜索整个文件系统树更优雅一些。

创建一个自定义注释,用于构建应用它的类的目录 - 类似于 @ClassCatalog。

评论

1赞 Daniel 5/10/2021
不如告诉我们 Spring 是如何做到这一点的。否则这只是道听途说。
0赞 Maksim Kostromin 5/28/2019 #23

纯 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:为了简化处理错误等的样板,我在这里使用和库vavrlombok

其他实现可以在我的 GitHub 中找到 daggerok/java-reflection-find-annotated-classes-or-methods 存储库

11赞 user4718768 11/9/2019 #24

这个怎么样:

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

然后,您可以重载该函数:

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

如果需要测试它:

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

如果 IDE 没有导入帮助程序:

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;

它的工作原理:

  • 从 IDE

  • 对于 JAR 文件

  • 无外部依赖

评论

3赞 Java Main 1/5/2021
要在 Windows 上工作,您必须将 file.toString().replace('/', '.') 更改为最终的字符串路径 = file.toString().replace('\\, '.');
0赞 Raiden Core 10/2/2021
这应该是一个答案,也只有一个答案!
3赞 botenvouwer 11/18/2019 #25

对于如此简单的事情,我找不到一个简短的工作片段。所以这里是我自己搞砸了一段时间后制作的:

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

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

它使用 .org.reflections

评论

1赞 lukas84 12/7/2021
请注意,这在 Reflections 0.9.12 中效果很好,但在版本 0.10 中找不到任何类型(其中 SubTypesScanner 已被弃用)。
4赞 Black 4/30/2020 #26

如果你在Spring-land,你可以使用PathMatchingResourcePatternResolver;

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

    Arrays.asList(resources).forEach(r->{
        ...
    });
0赞 ognjenkl 4/4/2022 #27

截至版本:org.reflections0.10

org.reflections.scanners.SubTypesScanner 

org.reflections.Reflections.getAllTypes() 

已弃用。我使用:

public Set<String> getEntityNamesInPackage(String packagePath) {
    Reflections reflections = new Reflections(new ConfigurationBuilder()
                    .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                    .setUrls(ClasspathHelper.forPackage(packagePath))
                    .setScanners(SubTypes.filterResultsBy(s -> true)));

    return reflections.getAll(SubTypes).stream()
            .filter(s -> s.startsWith(packagePath))
            .collect(Collectors.toSet());
}
1赞 Patrick Hechler 4/16/2022 #28

这将扫描类加载器和所有父加载器以查找 JAR 文件和目录。 还会加载 jar 的 Class-Path 引用的 jar 文件和目录。

此代码是 Java 8、11、18 的测试版。 在 8 上,使用 URLClassLoader 和 getURLs() 方法,一切都可以完美运行。 在 11 上,它使用反射工作正常,但 JVM 在 stderr 流上打印警告(不能用我的 JVM 使用 System.setErr() 重定向) 在 18 上,反射是无用的(抛出 NoSuchMethod/Field),唯一的事情(我知道它有效的地方)是使用 getResource() 方法。当类装入器从文件系统加载给定包的资源时,将返回一个简单的路径 url。当类加载器从 jar 加载资源时,会返回类似 'jar:file:[jar-path]!/[in-jar-path]' 的 url。

我使用了答案 https://stackoverflow.com/a/1157352/18252455(来自重复的问题)并添加了读取类路径和搜索目录 URL 的功能。

/**
 * orig description:<br>
 * Scans all classloaders for the current thread for loaded jars, and then scans
 * each jar for the package name in question, listing all classes directly under
 * the package name in question. Assumes directory structure in jar file and class
 * package naming follow java conventions (i.e. com.example.test.MyTest would be in
 * /com/example/test/MyTest.class)
 * <p>
 * in addition this method also scans for directories, where also is assumed, that the classes are
 * placed followed by the java conventions. (i.e. <code>com.example.test.MyTest</code> would be in
 * <code>directory/com/example/test/MyTest.class</code>)
 * <p>
 * this method also reads the jars Class-Path for other jars and directories. for the jars and
 * directories referred in the jars are scanned with the same rules as defined here.<br>
 * it is ensured that no jar/directory is scanned exactly one time.
 * <p>
 * if {@code bailError} is <code>true</code> all errors will be wrapped in a
 * {@link RuntimeException}
 * and then thrown.<br>
 * a {@link RuntimeException} will also be thrown if something unexpected happens.<br>
 * 
 * @param packageName
 *            the name of the package for which the classes should be searched
 * @param allowSubPackages
 *            <code>true</code> is also classes in sub packages should be found
 * @param loader
 *            the {@link ClassLoader} which should be used to find the URLs and to load classes
 * @param bailError
 *            if all {@link Exception} should be re-thrown wrapped in {@link RuntimeException} and
 *            if a {@link RuntimeException} should be thrown, when something is not as expected.
 * @see https://stackoverflow.com/questions/1156552/java-package-introspection
 * @see https://stackoverflow.com/a/1157352/18252455
 * @see https://creativecommons.org/licenses/by-sa/2.5/
 * @see https://creativecommons.org/licenses/by-sa/2.5/legalcode
 */
public static Set <Class <?>> tryGetClassesForPackage(String packageName, boolean allowSubPackages, ClassLoader loader, boolean bailError) {
    Set <URL> jarUrls = new HashSet <URL>();
    Set <Path> directorys = new HashSet <Path>();
    findClassPools(loader, jarUrls, directorys, bailError, packageName);
    Set <Class <?>> jarClasses = findJarClasses(allowSubPackages, packageName, jarUrls, directorys, loader, bailError);
    Set <Class <?>> dirClasses = findDirClasses(allowSubPackages, packageName, directorys, loader, bailError);
    jarClasses.addAll(dirClasses);
    return jarClasses;
}

private static Set <Class <?>> findDirClasses(boolean subPackages, String packageName, Set <Path> directorys, ClassLoader loader, boolean bailError) {
    Filter <Path> filter;
    Set <Class <?>> result = new HashSet <>();
    for (Path startPath : directorys) {
        String packagePath = packageName.replace(".", startPath.getFileSystem().getSeparator());
        final Path searchPath = startPath.resolve(packagePath).toAbsolutePath();
        if (subPackages) {
            filter = p -> {
                p = p.toAbsolutePath();
                Path other;
                if (p.getNameCount() >= searchPath.getNameCount()) {
                    other = searchPath;
                } else {
                    other = searchPath.subpath(0, p.getNameCount());
                }
                if (p.startsWith(other)) {
                    return true;
                } else {
                    return false;
                }
            };
        } else {
            filter = p -> {
                p = p.toAbsolutePath();
                if (p.getNameCount() > searchPath.getNameCount() + 1) {
                    return false;
                } else if (p.toAbsolutePath().startsWith(searchPath)) {
                    return true;
                } else {
                    return false;
                }
            };
        }
        if (Files.exists(searchPath)) {
            findDirClassFilesRecursive(filter, searchPath, startPath, result, loader, bailError);
        } // the package does not have to exist in every directory
    }
    return result;
}

private static void findDirClassFilesRecursive(Filter <Path> filter, Path path, Path start, Set <Class <?>> classes, ClassLoader loader, boolean bailError) {
    try (DirectoryStream <Path> dirStream = Files.newDirectoryStream(path, filter)) {
        for (Path p : dirStream) {
            if (Files.isDirectory(p)) {
                findDirClassFilesRecursive(filter, p, start, classes, loader, bailError);
            } else {
                Path subp = p.subpath(start.getNameCount(), p.getNameCount());
                String str = subp.toString();
                if (str.endsWith(".class")) {
                    str = str.substring(0, str.length() - 6);
                    String sep = p.getFileSystem().getSeparator();
                    if (str.startsWith(sep)) {
                        str = str.substring(sep.length());
                    }
                    if (str.endsWith(sep)) {
                        str = str.substring(0, str.length() - sep.length());
                    }
                    String fullClassName = str.replace(sep, ".");
                    try {
                        Class <?> cls = Class.forName(fullClassName, false, loader);
                        classes.add(cls);
                    } catch (ClassNotFoundException e) {
                        if (bailError) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    } catch (IOException e) {
        if (bailError) {
            throw new RuntimeException(e);
        }
    }
}

private static Set <Class <?>> findJarClasses(boolean subPackages, String packageName, Set <URL> nextJarUrls, Set <Path> directories, ClassLoader loader, boolean bailError) {
    String packagePath = packageName.replace('.', '/');
    Set <Class <?>> result = new HashSet <>();
    Set <URL> allJarUrls = new HashSet <>();
    while (true) {
        Set <URL> thisJarUrls = new HashSet <>(nextJarUrls);
        thisJarUrls.removeAll(allJarUrls);
        if (thisJarUrls.isEmpty()) {
            break;
        }
        allJarUrls.addAll(thisJarUrls);
        for (URL url : thisJarUrls) {
            try (JarInputStream stream = new JarInputStream(url.openStream())) {
                // may want better way to open url connections
                readJarClassPath(stream, nextJarUrls, directories, bailError);
                JarEntry entry = stream.getNextJarEntry();
                while (entry != null) {
                    String name = entry.getName();
                    int i = name.lastIndexOf("/");
                    
                    if (i > 0 && name.endsWith(".class")) {
                        try {
                            if (subPackages) {
                                if (name.substring(0, i).startsWith(packagePath)) {
                                    Class <?> cls = Class.forName(name.substring(0, name.length() - 6).replace("/", "."), false, loader);
                                    result.add(cls);
                                }
                            } else {
                                if (name.substring(0, i).equals(packagePath)) {
                                    Class <?> cls = Class.forName(name.substring(0, name.length() - 6).replace("/", "."), false, loader);
                                    result.add(cls);
                                }
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    entry = stream.getNextJarEntry();
                }
                stream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return result;
}

private static void readJarClassPath(JarInputStream stream, Set <URL> jarUrls, Set <Path> directories, boolean bailError) {
    Object classPathObj = stream.getManifest().getMainAttributes().get(new Name("Class-Path"));
    if (classPathObj == null) {
        return;
    }
    if (classPathObj instanceof String) {
        String[] entries = ((String) classPathObj).split("\\s+");// should also work with a single space (" ")
        for (String entry : entries) {
            try {
                URL url = new URL(entry);
                addFromUrl(jarUrls, directories, url, bailError);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
    } else if (bailError) {
        throw new RuntimeException("the Class-Path attribute is no String: " + classPathObj.getClass().getName() + " tos='" + classPathObj + "'");
    }
}

private static void findClassPools(ClassLoader classLoader, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, String packageName) {
    packageName = packageName.replace('.', '/');
    while (classLoader != null) {
        if (classLoader instanceof URLClassLoader) {
            for (URL url : ((URLClassLoader) classLoader).getURLs()) {
                addFromUrl(jarUrls, directoryPaths, url, bailError);
                System.out.println("rurl-class-loade.url[n]r->'" + url + "'");
            }
        } else {
            URL res = classLoader.getResource("");
            if (res != null) {
                addFromUrl(jarUrls, directoryPaths, res, bailError);
            }
            res = classLoader.getResource("/");
            if (res != null) {
                addFromUrl(jarUrls, directoryPaths, res, bailError);
            }
            res = classLoader.getResource("/" + packageName);
            if (res != null) {
                res = removePackageFromUrl(res, packageName, bailError);
                if (res != null) {
                    addFromUrl(jarUrls, directoryPaths, res, bailError);
                }
            }
            res = classLoader.getResource(packageName);
            if (res != null) {
                res = removePackageFromUrl(res, packageName, bailError);
                if (res != null) {
                    addFromUrl(jarUrls, directoryPaths, res, bailError);
                }
            }
            addFromUnknownClass(classLoader, jarUrls, directoryPaths, bailError, 8);
        }
        classLoader = classLoader.getParent();
    }
}

private static URL removePackageFromUrl(URL res, String packagePath, boolean bailError) {
    packagePath = "/" + packagePath;
    String urlStr = res.toString();
    if ( !urlStr.endsWith(packagePath)) {
        if (bailError) {
            throw new RuntimeException("the url string does not end with the packagepath! packagePath='" + packagePath + "' urlStr='" + urlStr + "'");
        } else {
            return null;
        }
    }
    urlStr = urlStr.substring(0, urlStr.length() - packagePath.length());
    if (urlStr.endsWith("!")) {
        urlStr = urlStr.substring(0, urlStr.length() - 1);
    }
    if (urlStr.startsWith("jar:")) {
        urlStr = urlStr.substring(4);
    }
    try {
        return new URL(urlStr);
    } catch (MalformedURLException e) {
        if (bailError) {
            throw new RuntimeException(e);
        } else {
            return null;
        }
    }
}

private static void addFromUnknownClass(Object instance, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, int maxDeep) {
    Class <?> cls = instance.getClass();
    while (cls != null) {
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            Class <?> type = field.getType();
            Object value;
            try {
                value = getValue(instance, field);
                if (value != null) {
                    addFromUnknownValue(value, jarUrls, directoryPaths, bailError, type, field.getName(), maxDeep - 1);
                }
            } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
                if (bailError) {
                    final String version = System.getProperty("java.version");
                    String vers = version;
                    if (vers.startsWith("1.")) {
                        vers = vers.substring(2);
                    }
                    int dotindex = vers.indexOf('.');
                    if (dotindex != -1) {
                        vers = vers.substring(0, dotindex);
                    }
                    int versNum;
                    try {
                        versNum = Integer.parseInt(vers);
                    } catch (NumberFormatException e1) {
                        throw new RuntimeException("illegal version: '" + version + "' lastError: " + e.getMessage(), e);
                    }
                    if (versNum <= 11) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        cls = cls.getSuperclass();
    }
    
}

private static Object getValue(Object instance, Field field) throws IllegalArgumentException, IllegalAccessException, SecurityException {
    try {
        boolean flag = field.isAccessible();
        boolean newflag = flag;
        try {
            field.setAccessible(true);
            newflag = true;
        } catch (Exception e) {}
        try {
            return field.get(instance);
        } finally {
            if (flag != newflag) {
                field.setAccessible(flag);
            }
        }
    } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
        try {
            Field override = AccessibleObject.class.getDeclaredField("override");
            boolean flag = override.isAccessible();
            boolean newFlag = flag;
            try {
                override.setAccessible(true);
                flag = true;
            } catch (Exception s) {}
            override.setBoolean(field, true);
            if (flag != newFlag) {
                override.setAccessible(flag);
            }
            return field.get(instance);
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e1) {
            e.addSuppressed(e1);
            throw e;
        }
    }
}

private static void addFromUnknownValue(Object value, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, Class <?> type, String fieldName, int maxDeep) {
    if (Collection.class.isAssignableFrom(type)) {
        for (Object obj : (Collection <?>) value) {
            URL url = null;
            try {
                if (obj instanceof URL) {
                    url = (URL) obj;
                } else if (obj instanceof Path) {
                    url = ((Path) obj).toUri().toURL();
                } else if (obj instanceof File) {
                    url = ((File) obj).toURI().toURL();
                }
            } catch (MalformedURLException e) {
                if (bailError) {
                    throw new RuntimeException(e);
                }
            }
            if (url != null) {
                addFromUrl(jarUrls, directoryPaths, url, bailError);
            }
        }
    } else if (URL[].class.isAssignableFrom(type)) {
        for (URL url : (URL[]) value) {
            addFromUrl(jarUrls, directoryPaths, url, bailError);
        }
    } else if (Path[].class.isAssignableFrom(type)) {
        for (Path path : (Path[]) value) {
            try {
                addFromUrl(jarUrls, directoryPaths, path.toUri().toURL(), bailError);
            } catch (MalformedURLException e) {
                if (bailError) {
                    throw new RuntimeException(e);
                }
            }
        }
    } else if (File[].class.isAssignableFrom(type)) {
        for (File file : (File[]) value) {
            try {
                addFromUrl(jarUrls, directoryPaths, file.toURI().toURL(), bailError);
            } catch (MalformedURLException e) {
                if (bailError) {
                    throw new RuntimeException(e);
                }
            }
        }
    } else if (maxDeep > 0) {
        addFromUnknownClass(value, jarUrls, directoryPaths, bailError, maxDeep - 1);
    }
}

private static void addFromUrl(Set <URL> jarUrls, Set <Path> directoryPaths, URL url, boolean bailError) {
    if (url.getFile().endsWith(".jar") || url.getFile().endsWith(".zip")) {
        // may want better way to detect jar files
        jarUrls.add(url);
    } else {
        try {
            Path path = Paths.get(url.toURI());
            if (Files.isDirectory(path)) {
                directoryPaths.add(path);
            } else if (bailError) {
                throw new RuntimeException("unknown url for class loading: " + url);
            }
        } catch (URISyntaxException e) {
            if (bailError) {
                throw new RuntimeException(e);
            }
        }
    }
}

进口:

import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
2赞 lost.sof 5/10/2022 #29

定义要在包中扫描的类test

package test;

public class A {
  private class B {}

  enum C {}

  record D() {}
}

对于 ,它对我的工作方式如下:org.reflections:reflections:0.10.2

使用 reflection lib 扫描包中的类test

@Test
void t() {
  final String packagePath = "test";
  final Reflections reflections =
    new Reflections(packagePath, Scanners.SubTypes.filterResultsBy(v -> true));
  reflections.getAll(Scanners.SubTypes).forEach(System.out::println);
}

输出

java.lang.constant.Constable
java.lang.Enum
java.lang.Comparable
java.lang.Record
java.lang.Object
java.io.Serializable
test.A$C
test.A$D
test.A$B
test.A

对于 ,它对我的工作方式如下:io.github.classgraph:classgraph:4.8.146


@Test
void t() {
  final String packagePath = "test";
  try (ScanResult scanResult = new ClassGraph()
    .enableClassInfo()
    .ignoreClassVisibility()
    .acceptPackages(packagePath)
    .scan()) {
    
    scanResult.getAllClasses()
      .forEach(v -> {
        System.out.println(v.getName());
      });
  }
}

输出

test.A
test.A$B
test.A$C
test.A$D