获取类路径中的所有类

52

如何在运行时获取CLASSPATH中所有可用类的列表?
在Eclipse IDE中,您可以通过按下Ctrl+Shift+T来执行此操作。
在Java中是否有任何方法可以完成此操作?

4个回答

60

通过将空字符串传递给 ClassLoader#getResources(),可以获取所有类路径根目录。

Enumeration<URL> roots = classLoader.getResources("");

你可以按以下方法基于URL构建一个File

File root = new File(url.getPath());

你可以使用File#listFiles()来获取给定目录中的所有文件列表:

for (File file : root.listFiles()) {
    // ...
}
您可以使用标准的 java.io.File 方法来检查它是否为目录和/或获取文件名。
if (file.isDirectory()) {
    // Loop through its listFiles() recursively.
} else {
    String name = file.getName();
    // Check if it's a .class file or a .jar file and handle accordingly.
}

根据唯一的功能需求,我猜测Reflections库更符合您的需求。


4
“classpath roots” 是什么意思?我注意到 classLoader.getResources("") 只返回类路径中包含我自己编写的 .class 文件的一个或两个目录(而不是第三方包中的目录)。 - flow2k
如果我们想要完整的类路径列表,难道不应该使用 URL[] urls = ((URLClassLoader) classLoader).getURLs() 吗? - flow2k

22

这是我编写的代码来实现它。如果你在类路径上做了一些奇怪的事情,我相信它可能无法获取所有内容,但对于我来说似乎很有效。请注意,它实际上并不加载类,只返回它们的名称。这是为了避免将所有类加载到内存中,并且因为我们公司代码库中的某些类在错误的时间加载会导致初始化错误...

public interface Visitor<T> {
    /**
     * @return {@code true} if the algorithm should visit more results,
     * {@code false} if it should terminate now.
     */
    public boolean visit(T t);
}

public class ClassFinder {
    public static void findClasses(Visitor<String> visitor) {
        String classpath = System.getProperty("java.class.path");
        String[] paths = classpath.split(System.getProperty("path.separator"));

        String javaHome = System.getProperty("java.home");
        File file = new File(javaHome + File.separator + "lib");
        if (file.exists()) {
            findClasses(file, file, true, visitor);
        }

        for (String path : paths) {
            file = new File(path);
            if (file.exists()) {
                findClasses(file, file, false, visitor);
            }
        }
    }

    private static boolean findClasses(File root, File file, boolean includeJars, Visitor<String> visitor) {
        if (file.isDirectory()) {
            for (File child : file.listFiles()) {
                if (!findClasses(root, child, includeJars, visitor)) {
                    return false;
                }
            }
        } else {
            if (file.getName().toLowerCase().endsWith(".jar") && includeJars) {
                JarFile jar = null;
                try {
                    jar = new JarFile(file);
                } catch (Exception ex) {

                }
                if (jar != null) {
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();
                        int extIndex = name.lastIndexOf(".class");
                        if (extIndex > 0) {
                            if (!visitor.visit(name.substring(0, extIndex).replace("/", "."))) {
                                return false;
                            }
                        }
                    }
                }
            }
            else if (file.getName().toLowerCase().endsWith(".class")) {
                if (!visitor.visit(createClassName(root, file))) {
                    return false;
                }
            }
        }

        return true;
    }

    private static String createClassName(File root, File file) {
        StringBuffer sb = new StringBuffer();
        String fileName = file.getName();
        sb.append(fileName.substring(0, fileName.lastIndexOf(".class")));
        file = file.getParentFile();
        while (file != null && !file.equals(root)) {
            sb.insert(0, '.').insert(0, file.getName());
            file = file.getParentFile();
        }
        return sb.toString();
    }
}

如何使用它:

ClassFinder.findClasses(new Visitor<String>() {
    @Override
    public boolean visit(String clazz) {
        System.out.println(clazz)
        return true; // return false if you don't want to see any more classes
    }
});

2
你应该使用 String[] paths = classpath.split( System.getProperty("path.separator") ); 否则在Linux上无法正常工作。 - sherif
我想试一试。你能展示一下如何使用 ClassFinder 的代码吗? - Mark Cramer
这太糟糕了。访问者接口的实现在哪里? - belgariontheking
@belgariontheking 调用 findClasses 的代码应该创建一个实现(通常是匿名类)。这与您在 JavaScript 中传递给 forEach 的回调非常相似。我本可以编写该方法以返回 Iterable,但这将更加复杂(如果 Java 像许多其他现代语言一样具有生成器函数,则可以简单地使其返回 Iterable)。 - Andy
File.separator 不是更好吗?这样代码就更具可移植性了。 - mhradek

12

您可以使用Guava库中com.google.common.reflect包中的实用程序类。例如,要获取特定包中的所有类:

    ClassLoader cl = getClass().getClassLoader();
    Set<ClassPath.ClassInfo> classesInPackage = ClassPath.from(cl).getTopLevelClassesRecursive("com.mycompany.mypackage");

这是简明的,然而其他答案所描述的相同警告仍然适用,即它通常只会找到由“标准”ClassLoader(例如URLClassLoader)加载的类。


1

我偶尔会寻找这个。这有点困难,因为即使你设法在类路径上找到了所有东西,你可能也找不到给定类加载器可用的所有内容(例如,我曾经在一个项目中直接从数据库加载类定义)。

目前最好的选择可能是研究Spring。他们扫描类路径上的类,以查看是否有注释需要启动一些内容。

这里的被接受答案是开始的好地方:

在运行时扫描Java注释


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接