如何枚举包中的所有类并将它们添加到List?

时间:2008-10-06 22:52:23

标签: java

我需要枚举包中的所有类并将它们添加到List中。单个类的非动态版本如下:

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

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


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

更新: 再次阅读本文,我可以看到它是如何被误读的。我想在编译后从文件系统中枚举所有MY PROJECT'S类。

8 个答案:

答案 0 :(得分:37)

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

好的,我终于开始清理下面的代码片段了。我坚持使用它自己的github项目,甚至添加了测试。

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

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

有关功能更强大且功能更丰富的类路径扫描程序,请参阅https://github.com/lukehutch/fast-classpath-scanner/wiki。我建议先阅读我的代码片段以获得高层次的理解,然后使用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;
}

答案 1 :(得分:3)

我想出了如何做到这一点。这是程序:

  1. 从根包中的类开始,从类加载器
  2. 获取它所在的文件夹
  3. 递归枚举此文件夹中的所有.class文件
  4. 将文件名转换为完全限定的类名
  5. 使用Class.forName()获取类
  6. 这里有一些令人讨厌的技巧让我有些不安,但它有效 - 例如:

    1. 使用字符串操作将路径名转换为包名称
    2. 对根软件包名称进行硬编码以启用剥离路径前缀
    3. 太糟糕了,stackoverflow不允许我接受我自己的答案......

答案 2 :(得分:3)

你可以试试我的图书馆FastClasspathScanner

要枚举包中的所有类,请执行以下操作:

Set<String> classNames = new FastClassPathScanner("com.mypackage")
    .scan()
    .getNamesOfAllClasses();

答案 3 :(得分:2)

我担心你必须手动扫描类路径以及java搜索类的其他地方(例如,ext目录或引导类路径)。 由于java使用延迟加载类,因此它甚至可能不知道包中尚未加载的其他类。 还要检查“密封”包装的概念。

答案 4 :(得分:2)

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

答案 5 :(得分:2)

有一点需要注意:像 tomcat和JBoss这样的ApplicationEngines / servlet容器具有分层类加载器。获取系统类加载器是行不通的。

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

的类碰撞

仅作为一个例子。如果所有类都进入了一个超级类上下文,那么你就不知道你是否使用了适用于版本1或版本2的类。

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

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

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

答案 6 :(得分:0)

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

可能值得查看一下开源项目,这些项目似乎可以进行所需的枚举(如Eclipse)以获取灵感。

答案 7 :(得分:0)

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

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

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

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