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

时间:2022-09-25 10:32:58

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中。单个类的非动态版本如下:

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'S类。

8 个解决方案

#1


37  

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

****UPDATE 2 (2016)****

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

For an even more robust and feature-rich classpath scanner, see https://github.com/lukehutch/fast-classpath-scanner/wiki. 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/lukehutch/fast-classpath-scanner/wiki。我建议先阅读我的代码片段以获得高层次的理解,然后使用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.

严格来说,无法在包中列出类。这是因为包实际上只是一个命名空间(例如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;
}

#2


3  

I figured out how to do this. Here's the procedure:

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

  1. Start with a class in the root package, and get the folder it's in from the class loader
  2. 从根包中的类开始,从类加载器中获取它所在的文件夹

  3. Recursively enumerate all .class files in this folder
  4. 递归枚举此文件夹中的所有.class文件

  5. Convert the file names to fully qualified class names
  6. 将文件名转换为完全限定的类名

  7. Use Class.forName() to get the classes
  8. 使用Class.forName()来获取类

There are a few nasty tricks here that make me a bit uneasy, but it works - for example:

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

  1. Converting path names to package names using string manipulation
  2. 使用字符串操作将路径名转换为包名

  3. Hard-coding the root package name to enable stripping away the path prefix
  4. 对根软件包名称进行硬编码以启用剥离路径前缀

Too bad that * doesn't allow me to accept my own answer...

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

#3


3  

You could try my library FastClasspathScanner.

您可以尝试我的库FastClasspathScanner。

To enumerate all classes in a package, do the following:

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

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

#4


2  

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使用延迟加载类,因此它甚至可能不知道包中尚未加载的其他类。还要检查“密封”包装的概念。

#5


2  

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.

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

#6


2  

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 Tomcat works (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.

仅作为一个例子。如果所有类都进入了一个超级类上下文,那么你就不知道你是否使用了适用于版本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。这是层次结构。它首先检查本地应用程序上下文并将其向上移动(这是我目前的情况BTW)。

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

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

#7


0  

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)的灵感。

#8


0  

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.

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

#1


37  

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

****UPDATE 2 (2016)****

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

For an even more robust and feature-rich classpath scanner, see https://github.com/lukehutch/fast-classpath-scanner/wiki. 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/lukehutch/fast-classpath-scanner/wiki。我建议先阅读我的代码片段以获得高层次的理解,然后使用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.

严格来说,无法在包中列出类。这是因为包实际上只是一个命名空间(例如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;
}

#2


3  

I figured out how to do this. Here's the procedure:

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

  1. Start with a class in the root package, and get the folder it's in from the class loader
  2. 从根包中的类开始,从类加载器中获取它所在的文件夹

  3. Recursively enumerate all .class files in this folder
  4. 递归枚举此文件夹中的所有.class文件

  5. Convert the file names to fully qualified class names
  6. 将文件名转换为完全限定的类名

  7. Use Class.forName() to get the classes
  8. 使用Class.forName()来获取类

There are a few nasty tricks here that make me a bit uneasy, but it works - for example:

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

  1. Converting path names to package names using string manipulation
  2. 使用字符串操作将路径名转换为包名

  3. Hard-coding the root package name to enable stripping away the path prefix
  4. 对根软件包名称进行硬编码以启用剥离路径前缀

Too bad that * doesn't allow me to accept my own answer...

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

#3


3  

You could try my library FastClasspathScanner.

您可以尝试我的库FastClasspathScanner。

To enumerate all classes in a package, do the following:

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

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

#4


2  

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使用延迟加载类,因此它甚至可能不知道包中尚未加载的其他类。还要检查“密封”包装的概念。

#5


2  

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.

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

#6


2  

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 Tomcat works (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.

仅作为一个例子。如果所有类都进入了一个超级类上下文,那么你就不知道你是否使用了适用于版本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。这是层次结构。它首先检查本地应用程序上下文并将其向上移动(这是我目前的情况BTW)。

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

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

#7


0  

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)的灵感。

#8


0  

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.

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