1. Overview
1.概述
Sometimes, we want to get information about the runtime behavior of our application, such as finding all classes available at runtime.
有时,我们想获得关于我们应用程序的运行时行为的信息,比如找到所有在运行时可用的类。
In this tutorial, we’ll explore several examples of how to find all classes in a Java package at runtime.
在本教程中,我们将探讨如何在运行时找到一个Java包中的所有类的几个例子。
2. Class Loaders
2.类装机
First, we’ll start our discussion with the Java class loaders. The Java class loader is part of the Java Runtime Environment (JRE) that dynamically loads Java classes into the Java Virtual Machine (JVM). The Java class loader decouples the JRE from knowing about files and file systems. Not all classes are loaded by a single class loader.
首先,我们将从Java类加载器开始讨论。Java类加载器是Java运行时环境(JRE)的一部分,它可以动态地将Java类加载到Java虚拟机(JVM)中。Java类加载器将JRE与对文件和文件系统的了解解耦。并不是所有的类都由一个类加载器加载。
Let’s understand the available class loaders in Java through pictorial representation:
让我们通过图示来了解Java中可用的类加载器。
Java 9 introduced some major changes to the class loaders. With the introduction of modules, we have the option to provide the module path alongside the classpath. The system class loader loads the classes that are present on the module path.
Java 9对类加载器引入了一些重大变化。随着模块的引入,我们可以选择在classpath旁边提供模块路径。系统类加载器会加载存在于模块路径上的类。
Class loaders are dynamic. They are not required to tell the JVM which classes it can provide at runtime. Hence, finding classes in a package is essentially a file system operation rather than one done by using Java Reflection.
类加载器是动态的。它们不需要告诉JVM在运行时可以提供哪些类。因此,在包中寻找类基本上是一种文件系统操作,而不是通过使用Java Reflection来完成。
However, we can write our own class loaders or examine the classpath to find classes inside a package.
然而,我们可以编写我们自己的类加载器,或者检查classpath来寻找包内的类。
3. Finding Classes in a Java Package
3.在Java包中寻找类
For our illustration, let’s create a package com.baeldung.reflection.access.packages.search
.
为了说明问题,让我们创建一个包com.baeldung.reflection.access.packages.search
。
Now, let’s define an example class:
现在,让我们来定义一个实例类。
public class ClassExample {
class NestedClass {
}
}
Next, let’s define an interface:
接下来,让我们定义一个接口。
public interface InterfaceExample {
}
In the next section, we’ll look at how to find classes using the system class loader and some third-party libraries.
在下一节中,我们将看看如何使用系统类加载器和一些第三方库来寻找类。
3.1. System Class Loader
3.1 系统类加载器
First, we’ll use the built-in system class loader. The system class loader loads all the classes found in the classpath. This happens during the early initialization of the JVM:
首先,我们将使用内置的系统类加载器。 系统类加载器加载在classpath中发现的所有类。这发生在JVM的早期初始化过程中。
public class AccessingAllClassesInPackage {
public Set<Class> findAllClassesUsingClassLoader(String packageName) {
InputStream stream = ClassLoader.getSystemClassLoader()
.getResourceAsStream(packageName.replaceAll("[.]", "/"));
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
return reader.lines()
.filter(line -> line.endsWith(".class"))
.map(line -> getClass(line, packageName))
.collect(Collectors.toSet());
}
private Class getClass(String className, String packageName) {
try {
return Class.forName(packageName + "."
+ className.substring(0, className.lastIndexOf('.')));
} catch (ClassNotFoundException e) {
// handle the exception
}
return null;
}
}
In our example above, we’re loading the system class loader using the static getSystemClassLoader() method.
在我们上面的例子中,我们使用静态的getSystemClassLoader()方法加载系统类加载器。
Next, we’ll find the resources in the given package. We’ll read the resources as a stream of URLs using the getResourceAsStream method. To fetch the resources under a package, we need to convert the package name to a URL string. So, we have to replace all the dots (.) with a path separator (“/”).
接下来,我们将找到指定包中的资源。我们将使用getResourceAsStream方法将资源作为URL流读取。为了获取一个包下的资源,我们需要将包的名称转换为URL字符串。因此,我们必须用路径分隔符(”/”)替换所有的点(.)。
After that, we’re going to input our stream to a BufferedReader and filter all the URLs with the .class extension. After getting the required resources, we’ll construct the class and collect all the results into a Set. Since Java doesn’t allow lambda to throw an exception, we have to handle it in the getClass method.
之后,我们将把我们的流输入到一个BufferedReader,并用.class扩展来过滤所有的URL。在获得所需的资源后,我们将构建类并将所有的结果收集到一个Set中。由于Java不允许lambda抛出异常,我们必须在getClassmethod中处理它。
Let’s now test this method:
现在我们来测试一下这个方法。
@Test
public void when_findAllClassesUsingClassLoader_thenSuccess() {
AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
Set<Class> classes = instance.findAllClassesUsingClassLoader(
"com.baeldung.reflection.access.packages.search");
Assertions.assertEquals(3, classes.size());
}
There are just two Java files in the package. However, we have three classes declared — including the nested class, NestedExample. As a result, our test resulted in three classes.
这个包里只有两个Java文件。然而,我们声明了三个类–包括嵌套类,NestedExample。因此,我们的测试结果是有三个类。
Note that the search package is different from the current working package.
注意,搜索包与当前工作包不同。
3.2. Reflections Library
3.2.反思图书馆
Reflections is a popular library that scans the current classpath and allows us to query it at runtime.
Reflections是一个流行的库,它可以扫描当前的classpath并允许我们在运行时查询。
Let’s start by adding the reflections dependency to our Maven project:
让我们先把reflections依赖项添加到我们的Maven项目中。
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
Now, let’s dive into the code sample:
现在,让我们深入了解一下代码样本。
public Set<Class> findAllClassesUsingReflectionsLibrary(String packageName) {
Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
return reflections.getSubTypesOf(Object.class)
.stream()
.collect(Collectors.toSet());
}
In this method, we’re initiating the SubTypesScanner class and fetching all subtypes of the Object class. Through this approach, we get more granularity when fetching the classes.
在这个方法中,我们要启动SubTypesScanner类,并获取Object类的所有子类型。通过这种方法,我们在获取类的时候可以得到更多的粒度。
Again, let’s test it out:
再次,让我们来测试一下。
@Test
public void when_findAllClassesUsingReflectionsLibrary_thenSuccess() {
AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
Set<Class> classes = instance.findAllClassesUsingReflectionsLibrary(
"com.baeldung.reflection.access.packages.search");
Assertions.assertEquals(3, classes.size());
}
Similar to our previous test, this test finds the classes declared in the given package.
与我们之前的测试类似,这个测试找到在给定包中声明的类。
Now, let’s move on to our next example.
现在,让我们继续下一个例子。
3.3. Google Guava Library
3.3.Google Guava库
In this section, we’ll see how to find classes using the Google Guava library. Google Guava provides a ClassPath utility class that scans the source of the class loader and finds all loadable classes and resources.
在本节中,我们将看到如何使用Google Guava库查找类。Google Guava提供了一个ClassPath实用类,它可以扫描类加载器的源码并找到所有可加载的类和资源。
First, let’s add the guava dependency to our project:
首先,让我们把guava依赖项添加到我们的项目中。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
Let’s dive into the code:
让我们深入了解一下代码。
public Set<Class> findAllClassesUsingGoogleGuice(String packageName) throws IOException {
return ClassPath.from(ClassLoader.getSystemClassLoader())
.getAllClasses()
.stream()
.filter(clazz -> clazz.getPackageName()
.equalsIgnoreCase(packageName))
.map(clazz -> clazz.load())
.collect(Collectors.toSet());
}
In the above method, we’re providing the system class loader as input to the ClassPath#from method. All the classes scanned by ClassPath are filtered based on the package name. The filtered classes are then loaded (but not linked or initialized) and collected into a Set.
在上面的方法中,我们提供系统类加载器作为ClassPath#from方法的输入。所有被ClassPath扫描的类都会根据包的名称进行过滤。然后,被过滤的类被加载(但没有链接或初始化)并被收集到一个Set中。
Let’s now test this method:
现在我们来测试一下这个方法。
@Test
public void when_findAllClassesUsingGoogleGuice_thenSuccess() throws IOException {
AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
Set<Class> classes = instance.findAllClassesUsingGoogleGuice(
"com.baeldung.reflection.access.packages.search");
Assertions.assertEquals(3, classes.size());
}
In addition, the Google Guava library provides getTopLevelClasses() and getTopLevelClassesRecursive() methods.
此外,Google Guava库提供了getTopLevelClasses() 和getTopLevelClassesRecursive() 方法。
It’s important to note that in all the above examples, package-info is included in the list of available classes if present under the package and annotated with one or more package-level annotations.
需要注意的是,在上述所有的例子中,如果包下存在着一个或多个包级注解,那么包-信息就会被包含在可用的类列表中。
The next section will discuss how to find classes in a Modular Application.
下一节将讨论如何在一个模块化应用程序中寻找类。
4. Finding Classes in a Modular Application
4.在一个模块化的应用程序中寻找类
The Java Platform Module System (JPMS) introduced us to a new level of access control through modules. Each package has to be explicitly exported to be accessed outside the module.
Java平台模块系统(JPMS)通过模块向我们介绍了一个新的访问控制水平。每个包都必须明确导出,才能在模块之外被访问。
In a modular application, each module can be one of named, unnamed, or automatic modules.
在一个模块化的应用程序中,每个模块可以是命名的、未命名的或自动的模块之一。
For the named and automatic modules, the built-in system class loader will have no classpath. The system class loader will search for classes and resources using the application module path.
对于命名和自动模块,内置的系统类加载器将没有classpath。系统类加载器将使用应用程序模块路径来搜索类和资源。
For an unnamed module, it will set the classpath to the current working directory.
对于一个未命名的模块,它将把classpath设置为当前工作目录。
4.1. Within a Module
4.1.在一个模块内
All packages in a module have visibility to other packages in the module. The code inside the module enjoys reflective access to all types and all their members.
一个模块中的所有包对该模块中的其他包都是可见的。模块内的代码享有对所有类型及其所有成员的反射性访问。
4.2. Outside a Module
4.2.在一个模块之外
Since Java enforces the most restrictive access, we have to explicitly declare packages using the export or open module declaration to get reflective access to the classes inside the module.
由于Java执行最严格的访问,我们必须使用export 或open module声明明确地声明包,以获得对模块内类的反射性访问。
For a normal module, the reflective access for exported packages (but not open ones) only provides access to public and protected types and all their members of the declared package.
对于一个正常的模块,出口包(但不是开放包)的反射性访问只提供对public 和protected 类型及其所有声明包成员的访问。
We can construct a module that exports the package that needs to be searched:
我们可以构建一个模块,导出需要搜索的包。
module my.module {
exports com.baeldung.reflection.access.packages.search;
}
For a normal module, the reflective access for open packages provides access to all types and their members of the declared package:
对于一个正常的模块,开放包的反射性访问提供了对所有类型和它们的声明包成员的访问。
module my.module {
opens com.baeldung.reflection.access.packages.search;
}
Likewise, an open module grants reflective access to all types and their members as if all packages had been opened. Let’s now open our entire module for reflective access:
同样地,一个开放的模块授予对所有类型及其成员的反思性访问,就像所有包都被打开一样。现在让我们打开我们的整个模块,进行反思性访问。
open module my.module{
}
Finally, after ensuring that the proper modular descriptions for accessing packages are provided for the module, any of the methods from the previous section can be used to find all available classes inside a package.
最后,在确保为模块提供了适当的访问包的模块描述后,可以使用上一节中的任何方法来寻找包内所有可用的类。
5. Conclusion
5.总结
In conclusion, we learned about class loaders and the different ways to find all classes in a package. Also, we discussed accessing packages in a modular application.
最后,我们了解了类加载器以及在一个包中找到所有类的不同方法。此外,我们还讨论了在一个模块化的应用程序中访问包。
As usual, all the code is available over on GitHub.
像往常一样,所有的代码都可以在GitHub上找到。