【问题标题】:How can I enumerate all classes in a package and add them to a List?如何枚举包中的所有类并将它们添加到列表中?
【发布时间】:2010-09-15 15:31:36
【问题描述】:

我需要枚举一个包中的所有类并将它们添加到一个列表中。单个类的非动态版本是这样的:

List allClasses = new ArrayList();
allClasses.add(String.class);

如何动态添加包中的所有类及其所有子包?


更新: 阅读了早期的答案后,我确实在尝试解决另一个次要问题,所以让我声明一下。我知道这是可能的,因为其他工具可以做到。请参阅新问题here

更新:再读一遍,我可以看出它是如何被误读的。我希望在编译后从文件系统中枚举所有 MY PROJECT 的类。

【问题讨论】:

标签: java


【解决方案1】:

****更新 1 (2012)****

好的,我终于开始清理下面的代码 sn-p 了。我将它嵌入到它自己的 github 项目中,甚至添加了测试。

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

****更新 2 (2016)****

有关更强大且功能丰富的类路径扫描器,请参阅https://github.com/classgraph/classgraph。我建议先阅读我的代码 sn-p 以获得更高层次的理解,然后将 lukehutch 的工具用于生产目的。

****原帖 (2010)****

严格来说,不可能列出中的类。这是因为包实际上只不过是一个命名空间(例如 com.epicapplications.foo.bar),并且类路径中的任何 jar 文件都可能将类添加到包中。更糟糕的是,类加载器会按需加载类,而类路径的一部分可能位于网络连接的另一端。

可以解决更严格的问题。例如,JAR 文件中的所有类,或 JAR 文件在特定包中定义的所有类。无论如何,这是更常见的情况。

不幸的是,没有任何框架代码可以使这项任务变得简单。您必须以类似于 ClassLoader 查找类定义的方式扫描文件系统。

网络上有很多关于普通目录中类文件的示例。如今,我们大多数人都使用 JAR 文件。

要使用 JAR 文件,试试这个...

private static ArrayList<Class<?>> getClassesForPackage(Package pkg) {
    String pkgname = pkg.getName();
    ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
    // Get a File object for the package
    File directory = null;
    String fullPath;
    String relPath = pkgname.replace('.', '/');
    System.out.println("ClassDiscovery: Package: " + pkgname + " becomes Path:" + relPath);
    URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
    System.out.println("ClassDiscovery: Resource = " + resource);
    if (resource == null) {
        throw new RuntimeException("No resource for " + relPath);
    }
    fullPath = resource.getFile();
    System.out.println("ClassDiscovery: FullPath = " + resource);

    try {
        directory = new File(resource.toURI());
    } catch (URISyntaxException e) {
        throw new RuntimeException(pkgname + " (" + resource + ") does not appear to be a valid URL / URI.  Strange, since we got it from the system...", e);
    } catch (IllegalArgumentException e) {
        directory = null;
    }
    System.out.println("ClassDiscovery: Directory = " + directory);

    if (directory != null && directory.exists()) {
        // Get the list of the files contained in the package
        String[] files = directory.list();
        for (int i = 0; i < files.length; i++) {
            // we are only interested in .class files
            if (files[i].endsWith(".class")) {
                // removes the .class extension
                String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6);
                System.out.println("ClassDiscovery: className = " + className);
                try {
                    classes.add(Class.forName(className));
                } 
                catch (ClassNotFoundException e) {
                    throw new RuntimeException("ClassNotFoundException loading " + className);
                }
            }
        }
    }
    else {
        try {
            String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
            JarFile jarFile = new JarFile(jarPath);         
            Enumeration<JarEntry> entries = jarFile.entries();
            while(entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();
                if(entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) {
                    System.out.println("ClassDiscovery: JarEntry: " + entryName);
                    String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
                    System.out.println("ClassDiscovery: className = " + className);
                    try {
                        classes.add(Class.forName(className));
                    } 
                    catch (ClassNotFoundException e) {
                        throw new RuntimeException("ClassNotFoundException loading " + className);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
        }
    }
    return classes;
}

【讨论】:

  • 感谢这段代码,对我帮助很大。但是,我发现了一个小问题:如果您的资源 URL 中有空格,它们会被 urlencoded(即,您的路径中最终会出现 %20)。当您使用directory = new File(fullPath); 构建目录文件时,这会导致directory.exists( ) 返回false,因为它会尝试查找与编码路径匹配的文字。这可以通过使用directory = new File(resource.toURI()); 来解决(使用适当的异常处理)。
  • 这不适用于位于 JAR 文件中的包,因为 File 在那里不起作用。
  • @iDemmel - 我在代码中使用 File 是针对目录的情况。 JAR 读取代码使用 JarFile。此代码支持这两种情况。另外,我之前在生产系统中使用过它,并且在这两种情况下都有效。
  • @ddopson: 它在 new File(resource.toURI()) 处失败,因为构造函数在遇到“jar:file:/opt/”时会抛出“IllegalArgumentException: URI is not hierarchy” app/someDir/test.jar!/test/Test” 类型的 URI,因为我的类路径仅使用 JAR 文件构建。看看这个答案:stackoverflow.com/questions/7574965/… + 我自己使用你的代码遇到过。
  • @iDemmel - 奇怪它在我的发行版上工作。 File 类的行为确实如您所描述的那样。 download.oracle.com/javase/1.4.2/docs/api/java/io/…。无论如何,我在代码中添加了一个案例,以便它适用于任何一种行为。希望这可以为您解决问题...
【解决方案2】:

目前列出给定包中所有类的最强大的机制是ClassGraph,因为它处理widest possible array of classpath specification mechanisms,包括新的JPMS 模块系统。 (我是作者。)

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

【讨论】:

    【解决方案3】:

    我想出了如何做到这一点。流程如下:

    1. 从根包中的一个类开始,从类加载器中获取它所在的文件夹
    2. 递归枚举此文件夹中的所有 .class 文件
    3. 将文件名转换为完全限定的类名
    4. 使用 Class.forName() 获取类

    这里有一些讨厌的技巧让我有点不安,但它确实有效 - 例如:

    1. 使用字符串操作将路径名转换为包名
    2. 对根包名称进行硬编码以启用去除路径前缀

    太糟糕了,stackoverflow 不允许我接受自己的答案...

    【讨论】:

    • 你是对的,但是有大量的复杂性需要处理。请参阅我对 FastClasspathScanner 的回答。
    【解决方案4】:

    恐怕您必须手动扫描类路径和 java 搜索类的其他位置(例如,ext 目录或引导类路径)。 由于 java 使用延迟加载类,它甚至可能不知道包中尚未加载的其他类。 还要检查“密封”包的概念。

    【讨论】:

      【解决方案5】:

      这个问题每隔一段时间就会出现一次,这很有趣。问题是这个关键字更适合命名为“命名空间”。 Java 包在任何时候都没有描述一个包含包中所有类的具体容器。它只是定义了一个标记,类可以使用该标记来声明它们是该包的成员。您必须搜索整个类路径(如另一个回复所示)以确定包中的所有类。

      【讨论】:

        【解决方案6】:

        对此有一个警告:tomcat 和 JBoss 等 ApplicationEngines/servlet 容器具有分层类加载器。获取系统类加载器不行。

        Tomcat 的工作方式(事情可能已经改变,但我目前的经验并没有让我相信其他情况)但是每个应用程序上下文都有自己的类加载器,因此应用程序“foo”的类不要与应用程序 'fooV2' 的类发生冲突

        仅作为示例。如果所有的类都被整合到一个 uber 类上下文中,那么您将不知道您使用的是适合版本 1 还是版本 2 的类。

        此外,每个人都需要访问系统类,如 java.lang.String。这就是层次结构。它首先检查本地应用程序上下文并将其向上移动(顺便说一句,这是我目前的情况)。

        要管理这个,更好的方法是:this.getClass().getClassloader()

        在我的例子中,我有一个需要在某些模块上进行自我发现的 web 服务,它们显然位于“this”webservice 上下文或系统上下文中。通过执行上述操作,我可以同时检查两者。通过仅获取系统类加载器,我无法访问任何应用程序类(因此我的资源为空)。

        【讨论】:

          【解决方案7】:

          看看 java.net.URLClassLoader 在做什么。它从不枚举类,它只是在被要求时尝试查找类。如果要枚举类,则需要获取类路径,将其拆分为目录和 jar 文件。扫描目录(及其子目录)和 jar 文件以查找名称为 *.class 的文件。

          可能值得查看似乎可以进行您想要的枚举的开源项目(如Eclipse)以获得灵感。

          【讨论】:

            【解决方案8】:

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

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

            话虽如此,作为从文件系统加载类列表的替代方法,只需在要加载的所有类中实现相同的接口,而不考虑包。这样,您就可以加载(并实例化)所有您想要的类,而不管它们在什么包中。

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-10-20
              • 1970-01-01
              • 2022-11-16
              • 2015-07-20
              • 1970-01-01
              • 2011-11-06
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多