Java 如何枚举包中的所有类并将它们添加到列表中?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/176527/
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
How can I enumerate all classes in a package and add them to a List?
提问by thvo
I need to enumerate all classes in a package and add them to a List. The non-dynamic version for a single class goes like this:
我需要枚举一个包中的所有类并将它们添加到一个列表中。单个类的非动态版本如下所示:
List allClasses = new ArrayList();
allClasses.add(String.class);
How can I do this dynamically to add all classes in a package and all its subpackages?
如何动态执行此操作以添加包及其所有子包中的所有类?
Update:Having read the early answers, it's absolutely true that I'm trying to solve another secondary problem, so let me state it. And I know this is possible since other tools do it. See new question here.
更新:阅读了早期的答案后,我正在尝试解决另一个次要问题是绝对正确的,所以让我陈述一下。我知道这是可能的,因为其他工具可以做到。在这里看到新问题。
Update:Reading this again, I can see how it's being misread. I'm looking to enumerate all of MY PROJECT'S classes from the file system after compilation.
更新:再读一遍,我可以看到它是如何被误读的。我希望在编译后枚举文件系统中的所有 MY PROJECT 类。
采纳答案by Dave Dopson
****UPDATE 1 (2012)****
****更新 1 (2012)****
OK, I've finally gotten around to cleaning up the code snippet below. I stuck it into it's own github project and even added tests.
好的,我终于有时间清理下面的代码片段了。我把它放在它自己的 github 项目中,甚至添加了测试。
https://github.com/ddopson/java-class-enumerator
https://github.com/ddopson/java-class-enumerator
****UPDATE 2 (2016)****
****更新 2 (2016)****
For an even more robust and feature-rich classpath scanner, see https://github.com/classgraph/classgraph. I'd recommend first reading my code snippet to gain a high level understanding, then using lukehutch's tool for production purposes.
有关更强大且功能丰富的类路径扫描器,请参阅https://github.com/classgraph/classgraph。我建议首先阅读我的代码片段以获得高层次的理解,然后将 lukehutch 的工具用于生产目的。
****Original Post (2010)****
****原帖 (2010)****
Strictly speaking, it isn't possible to list the classes in a package. This is because a package is really nothing more than a namespace (eg com.epicapplications.foo.bar), and any jar-file in the classpath could potentially add classes into a package. Even worse, the classloader will load classes on demand, and part of the classpath might be on the other side of a network connection.
严格来说,不可能列出package中的类。这是因为包实际上只不过是一个命名空间(例如 com.epicapplications.foo.bar),并且类路径中的任何 jar 文件都可能将类添加到包中。更糟糕的是,类加载器会按需加载类,并且部分类路径可能位于网络连接的另一端。
It is possible to solve a more restrictive problem. eg, all classes in a JAR file, or all classes that a JAR file defines within a particular package. This is the more common scenario anyways.
可以解决更严格的问题。例如,JAR 文件中的所有类,或 JAR 文件在特定包中定义的所有类。无论如何,这是更常见的场景。
Unfortunately, there isn't any framework code to make this task easy. You have to scan the filesystem in a manner similar to how the ClassLoader would look for class definitions.
不幸的是,没有任何框架代码可以简化这项任务。您必须以类似于 ClassLoader 查找类定义的方式扫描文件系统。
There are a lot of samples on the web for class files in plain-old-directories. Most of us these days work with JAR files.
网络上有很多关于普通旧目录中类文件的示例。如今,我们大多数人都使用 JAR 文件。
To get things working with JAR files, try this...
要处理 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;
}
回答by G B
I'm afraid you'll have to manually scan the classpath and the other places where java searches for classes (e.g., the ext directory or the boot classpath). Since java uses lazy loading of classes, it may not even know about additional classes in your packages that haven't been loaded yet. Also check the notion of "sealed" packages.
恐怕您必须手动扫描类路径和 java 搜索类的其他位置(例如,ext 目录或引导类路径)。由于 java 使用类的延迟加载,它甚至可能不知道包中尚未加载的其他类。还要检查“密封”包的概念。
回答by David M. Karr
It's funny that this question comes up every once in a while. The problem is that this keyword would have been more appropriately named "namespace". The Java package does not delineate a concrete container that holds all the classes in the package at any one time. It simply defines a token that classes can use to declare that they are a member of that package. You'd have to search through the entire classpath (as another reply indicated) to determine all the classes in a package.
有趣的是,这个问题每隔一段时间就会出现一次。问题是这个关键字应该更恰当地命名为“命名空间”。Java 包并没有描述一个具体的容器,它在任何时候都包含包中的所有类。它只是定义了一个令牌,类可以使用该令牌来声明它们是该包的成员。您必须搜索整个类路径(如另一个回复所示)以确定包中的所有类。
回答by thvo
I figured out how to do this. Here's the procedure:
我想出了如何做到这一点。这是程序:
- Start with a class in the root package, and get the folder it's in from the class loader
- Recursively enumerate all .class files in this folder
- Convert the file names to fully qualified class names
- Use Class.forName() to get the classes
- 从根包中的一个类开始,从类加载器中获取它所在的文件夹
- 递归枚举此文件夹中的所有 .class 文件
- 将文件名转换为完全限定的类名
- 使用 Class.forName() 获取类
There are a few nasty tricks here that make me a bit uneasy, but it works - for example:
这里有一些讨厌的技巧让我有点不安,但它有效 - 例如:
- Converting path names to package names using string manipulation
- Hard-coding the root package name to enable stripping away the path prefix
- 使用字符串操作将路径名转换为包名
- 硬编码根包名称以去除路径前缀
Too bad that stackoverflow doesn't allow me to accept my own answer...
太糟糕了,stackoverflow 不允许我接受我自己的答案......
回答by Diastrophism
Look at what java.net.URLClassLoader is doing. It never enumerates classes, it just tries to find classes when asked for one. If you want to enumerate the classes, then you will need to get the classpath, split it into directories and jar files. Scan the directories (and their subdirectories) and jar files for files with the name *.class.
看看 java.net.URLClassLoader 在做什么。它从不枚举类,它只是在需要时尝试查找类。如果要枚举类,则需要获取类路径,将其拆分为目录和 jar 文件。扫描目录(及其子目录)和 jar 文件以查找名称为 *.class 的文件。
It may be worth looking at open source projects which seem to do the enumeration you want (like Eclipse) for inspiration.
可能值得查看开源项目,这些项目似乎可以进行您想要的枚举(如Eclipse)以获取灵感。
回答by Christian Bongiorno
There is a caveat to this: ApplicationEngines/servlet containers like tomcat and JBoss have hierarchical class loaders. Getting the system class loader will not do.
对此有一个警告:像tomcat 和 JBoss这样的 ApplicationEngines/servlet 容器具有分层类加载器。获取系统类加载器是不行的。
The way Tomcatworks (things may have changed, but my current experience doesn't lead me to believe otherwise) but each application context has it's own class loader so that classes for application 'foo' don't collide with classes for application 'fooV2'
Tomcat 的工作方式(事情可能已经改变,但我目前的经验并没有让我相信其他情况)但是每个应用程序上下文都有自己的类加载器,因此应用程序“foo”的类不会与应用程序“fooV2”的类发生冲突'
Just as an example. If all the classes got munged into one uber class context then you would have no idea if you were using classes appropriate for version 1 or version 2.
举个例子。如果所有的类都混入一个 uber 类上下文,那么您将不知道您使用的是适用于版本 1 还是版本 2 的类。
In addition, each one needs access to system classes like java.lang.String. This is the hierarchy. It checks the local app context first and moves it's way up (this is my current situation BTW).
此外,每个人都需要访问像 java.lang.String 这样的系统类。这就是层次结构。它首先检查本地应用程序上下文并将其向上移动(这是我目前的情况顺便说一句)。
To manage this, a better approach would be: this.getClass().getClassloader()
为了管理这个,更好的方法是:this.getClass().getClassloader()
In my case I have a webservice that needs to do self-discovery on some modules and they obviously reside in 'this' webservice context or the system context. By doing the above I get to check both. By just getting the system classloader I don't get access to any of the application classes (and thus my resources are null).
就我而言,我有一个需要对某些模块进行自我发现的网络服务,它们显然位于“这个”网络服务上下文或系统上下文中。通过执行上述操作,我可以同时检查两者。通过获取系统类加载器,我无法访问任何应用程序类(因此我的资源为空)。
回答by Luke Hutchison
The most robust mechanism for listing all classes in a given package is currently ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author.)
列出给定包中所有类的最强大的机制目前是ClassGraph,因为它处理最广泛的类路径规范机制,包括新的 JPMS 模块系统。(我是作者。)
List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
.enableClassInfo().scan()) {
classNames = scanResult.getAllClasses().getNames();
}
回答by Rodney P. Barbati
If you are merely looking to load a group of related classes, then Spring can help you.
如果您只是想加载一组相关的类,那么 Spring 可以帮助您。
Spring can instantiate a list or map of all classes that implement a given interface in one line of code. The list or map will contain instances of all the classes that implement that interface.
Spring 可以在一行代码中实例化实现给定接口的所有类的列表或映射。列表或映射将包含实现该接口的所有类的实例。
That being said, as an alternative to loading the list of classes out of the file system, instead just implement the same interface in all the classes you want to load, regardless of package. That way, you can load (and instantiate) all the classes you desire regardless of what package they are in.
话虽如此,作为从文件系统加载类列表的替代方法,只需在要加载的所有类中实现相同的接口,而不管包是什么。这样,您就可以加载(并实例化)您想要的所有类,而不管它们在哪个包中。
On the other hand, if having them all in a package is what you want, then simply have all the classes in that package implement a given interface.
另一方面,如果将它们全部放在一个包中是您想要的,那么只需让该包中的所有类实现给定的接口即可。