1. 概述
在本教程中,我们将介绍 Classgraph 这个库的作用及其使用方式。
✅ 一句话总结:Classgraph 是一个用于扫描 Java classpath 中资源的高性能库,它能构建资源的元数据,并提供友好的 API 供操作这些元数据。
这种能力在 Spring 系列框架中非常常见,比如自动扫描并注册带有特定注解的类到 Spring 容器中。但 Classgraph 的用途远不止于此。你可以用它来:
- 查找所有带有某个注解的类
- 查找特定名称的资源文件(如配置文件、JSON 文件等)
- 获取类的方法、字段、注解等元信息
⚠️ 特别提醒:Classgraph 直接操作字节码,不会加载类到 JVM,也不依赖反射机制,因此性能非常优秀。
2. Maven 依赖
首先,我们需要在 pom.xml
中添加 Classgraph 的依赖:
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.153</version>
</dependency>
添加完成后,我们就可以开始使用其 API 实现各种扫描任务了。
3. 基本用法
使用 Classgraph 的基本流程分为三步:
- 配置扫描参数(如包路径)
- 执行扫描
- 处理扫描结果
我们先定义一个测试注解和一个使用该注解的类:
@Target({TYPE, METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String value() default "";
}
@TestAnnotation
public class ClassWithAnnotation {
}
接下来,我们使用 Classgraph 扫描所有带有 @TestAnnotation
注解的类:
try (ScanResult result = new ClassGraph()
.enableClassInfo()
.enableAnnotationInfo()
.whitelistPackages(getClass().getPackage().getName())
.scan()) {
ClassInfoList classInfos = result.getClassesWithAnnotation(TestAnnotation.class.getName());
assertThat(classInfos)
.extracting(ClassInfo::getName)
.contains(ClassWithAnnotation.class.getName());
}
这段代码的逻辑很清晰:
- 配置扫描器:启用类和注解信息,限定扫描包范围
- 执行扫描:调用
scan()
方法 - 获取结果:通过
getClassesWithAnnotation()
获取目标类列表
扫描结果 ScanResult
包含了丰富的信息,后续我们还会看到它支持的其他方法。
4. 按方法注解过滤
接下来我们看看如何查找带有特定方法注解的类。
先定义一个类:
public class MethodWithAnnotation {
@TestAnnotation
public void service() {
}
}
然后使用 getClassesWithMethodAnnotation()
方法进行查找:
try (ScanResult result = new ClassGraph()
.enableAllInfo()
.whitelistPackages(getClass().getPackage().getName())
.scan()) {
ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName());
assertThat(classInfos)
.extracting(ClassInfo::getName)
.contains(MethodWithAnnotation.class.getName());
}
这个方法返回的 ClassInfoList
包含了所有方法上带有目标注解的类。
5. 按注解参数过滤
我们还可以根据注解中的参数值进一步过滤目标类。
先定义两个类,分别使用不同参数值的 @TestAnnotation
:
public class MethodWithAnnotationParameterDao {
@TestAnnotation("dao")
public void service() {
}
}
public class MethodWithAnnotationParameterWeb {
@TestAnnotation("web")
public void service() {
}
}
然后在扫描结果中,通过 filter()
方法筛选出参数值为 "web"
的类:
try (ScanResult result = new ClassGraph()
.enableAllInfo()
.whitelistPackages(getClass().getPackage().getName())
.scan()) {
ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName());
ClassInfoList webClassInfos = classInfos.filter(classInfo -> {
return classInfo.getMethodInfo().stream().anyMatch(methodInfo -> {
AnnotationInfo annotationInfo = methodInfo.getAnnotationInfo(TestAnnotation.class.getName());
if (annotationInfo == null) {
return false;
}
return "web".equals(annotationInfo.getParameterValues().getValue("value"));
});
});
assertThat(webClassInfos)
.extracting(ClassInfo::getName)
.contains(MethodWithAnnotationParameterWeb.class.getName());
}
这里我们用到了 AnnotationInfo
和 MethodInfo
来访问注解参数值,从而实现更精确的筛选。
6. 按字段注解过滤
Classgraph 也支持查找字段上带有指定注解的类。
先定义一个类:
public class FieldWithAnnotation {
@TestAnnotation
private String s;
}
然后使用 getClassesWithFieldAnnotation()
方法进行查找:
try (ScanResult result = new ClassGraph()
.enableAllInfo()
.whitelistPackages(getClass().getPackage().getName())
.scan()) {
ClassInfoList classInfos = result.getClassesWithFieldAnnotation(TestAnnotation.class.getName());
assertThat(classInfos)
.extracting(ClassInfo::getName)
.contains(FieldWithAnnotation.class.getName());
}
非常简单直接,和方法注解类似,只是换了个 API。
7. 查找资源文件
除了扫描类,Classgraph 还能扫描 classpath 中的资源文件。
比如我们在 src/test/resources/classgraph/
下创建一个配置文件:
my.config
文件内容为:
my data
然后使用如下代码查找并读取该文件内容:
try (ScanResult result = new ClassGraph()
.whitelistPaths("classgraph")
.scan()) {
ResourceList resources = result.getResourcesWithExtension("config");
assertThat(resources)
.extracting(Resource::getPath)
.containsOnly("classgraph/my.config");
assertThat(resources.get(0).getContentAsString())
.isEqualTo("my data");
}
这里的 getResourcesWithExtension()
方法可以按扩展名查找资源,除此之外还有:
getAllResources()
:获取所有资源getResourcesWithPath()
:按路径查找getResourcesMatchingPattern()
:按正则表达式查找
这些方法返回的是 ResourceList
,我们可以从中获取 Resource
对象并操作其内容。
8. 实例化类
当我们需要实例化扫描到的类时,强烈建议使用 ClassInfo.loadClass()
方法,而不是 Class.forName()
。
原因很简单:Classgraph 使用自己的类加载器加载某些类(尤其是 JAR 中的类),如果使用 Class.forName()
,可能会导致同一个类被不同类加载器重复加载,进而引发各种奇怪的问题。
✅ 正确做法:
MyClass clazz = (MyClass) classInfo.loadClass().newInstance();
❌ 踩坑写法:
MyClass clazz = (MyClass) Class.forName(classInfo.getName()).newInstance();
9. 总结
在本文中,我们学习了如何使用 Classgraph 高效扫描 Java classpath 中的类和资源文件,并获取它们的元信息。
✅ Classgraph 的主要优势包括:
- 支持类、方法、字段、注解、资源的扫描
- 不依赖反射,不加载类到 JVM,性能高
- 提供丰富的 API,使用简单直观
- 支持多种过滤方式,满足复杂业务需求
如果你正在做自动注册、插件系统、代码分析、注解处理等相关功能,Classgraph 是一个非常值得尝试的工具。