当前位置: 首页 > backend >正文

classgraph:Java轻量级类和包扫描器

文章目录

  • 一、写在前面
  • 二、使用
    • 1、ClassGraph配置参数
    • 2、查找指定注解的类
    • 3、扫描接口、父类的子类
    • 4、查找类的方法、注解、字段
    • 5、使用过滤器+并交集
    • 6、读取类型注解
    • 7、扫描特定 URL
    • 8、查找和读取资源文件
    • 9、查找类路径或模块路径中的所有重复类定义

一、写在前面

开源地址:https://github.com/classgraph/classgraph
官方文档:https://github.com/classgraph/classgraph/wiki/Code-examples
参考文档:https://www.baeldung.com/classgraph

注意!ScanResult 实现了 AutoCloseable 接口,必须使用 try-with-resources 语法或手动调用 close() 方法释放资源,否则可能导致内存泄漏(尤其是在频繁扫描的场景中)。

// 正确用法
try (ScanResult result = new ClassGraph().scan()) {// 使用 result
}

注意!避免无限制扫描整个类路径(默认行为),这会导致扫描速度慢且消耗大量内存。

<dependency><groupId>io.github.classgraph</groupId><artifactId>classgraph</artifactId><version>4.8.181</version>
</dependency>

classgraph 的扫描过程本身不会初始化类,只有当你显式加载类并执行触发初始化的操作时,类才会被初始化。这一点与 Java 反射中 “Class.forName() 可能触发初始化” 的行为不同(Class.forName(String) 会初始化类,而 Class.forName(String, false, ClassLoader) 可以控制不初始化)。

try (ScanResult result = new ClassGraph().enableClassInfo().scan()) {ClassInfo classInfo = result.getClassInfo("com.example.MyClass");// 此时类未加载,更未初始化Class<?> clazz = classInfo.loadClass(); // 加载类(但不一定初始化)// 以下操作会触发类初始化Object instance = clazz.newInstance(); // 创建实例// 或访问静态字段/方法
}

二、使用

1、ClassGraph配置参数

import io.github.classgraph.*;public class Test {public static void main(String[] args) throws Exception {/*** 1、启动配置+ 扫描*/try (ScanResult scanResult =                // scanResult 必须使用 try-with-resourcesnew ClassGraph()                    // 创建 ClassGraph 实例//.verbose()                      // 打印日志(如果你想的话).enableAllInfo()                // 扫描 classes, methods, fields, annotations.acceptPackages("com.demo")      // 扫描的包.scan()) {                      // 开始扫描,返回 ScanResult// 获取指定类信息ClassInfo widgetClassInfo = scanResult.getClassInfo("com.demo.springbootdemo.TestController");// ...}}
}

2、查找指定注解的类

try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("com.xyz").scan()) {ClassInfoList routeClassInfoList = scanResult.getClassesWithAnnotation("com.xyz.Route");for (ClassInfo routeClassInfo : routeClassInfoList) {// 获取注解AnnotationInfo annotationInfo = routeClassInfo.getAnnotationInfo("com.xyz.Route");AnnotationParameterValueList paramVals = annotationInfo.getParameterValues();// Route注释有一个名为“path”的参数String routePath = paramVals.get("path");//或者,您可以加载并实例化注释,以便注释//可以直接调用方法来获取注释参数值(这设置//一个InvocationHandler,用于模拟Route注释实例,因为注释//如果不加载带注释的类,就不能直接实例化)。Route route = (Route) annotationInfo.loadClassAndInstantiate();String routePathDirect = route.path();// ...// 1、扫描指定了注解的类ClassInfoList classInfos = scanResult.getClassesWithAnnotation(TestAnnotation.class.getName());// getClassesWithMethodAnnotations() — 来查找所有被目标注解标记了方法的所有类ClassInfoList classInfos2 = scanResult.getClassesWithMethodAnnotation(TestAnnotation.class.getName());// 过滤,TestAnnotation注解的value值为web的ClassInfoList classInfos3 = scanResult.getClassesWithMethodAnnotation(TestAnnotation.class.getName());ClassInfoList webClassInfos = classInfos3.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"));});});// 查找所有元注解/*** 元注解用于注解注解。对于注解类 ClassInfo 的注解 ci ,可以通过调用 ci.getClassesWithAnnotation() 找到它注解的类,* 返回一个 ClassInfoList 。然后可以通过调用 .getAnnotations() 对该列表进行过滤,仅保留注解类,* 返回由 ci 注解且本身是注解的类列表。检查该列表是否为空可以测试 ci 是否为元注解:*/ClassInfoList metaAnnotations = scanResult.getAllAnnotations().filter(ci -> !ci.getClassesWithAnnotation().getAnnotations().isEmpty());// 使用`getClassesWithFieldAnnotation()`方法根据字段注解来过滤`ClassInfoList`结果// 查找字段上有TestAnnotation 注解的类ClassInfoList classInfos4 = scanResult.getClassesWithFieldAnnotation(TestAnnotation.class.getName());}
}

3、扫描接口、父类的子类

try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages(Test.class.getPackage().getName()).scan()) {// 获取所有实现了某接口的类ClassInfoList widgetClasses = scanResult.getClassesImplementing("com.xyz.Widget");// 获取指定超类所有的子类/*** 注意!!!加载的时候一定要用loadClasses方法加载类,而不是Class.forName(className)!!!*/ClassInfoList controlClasses = scanResult.getSubclasses("com.xyz.Control");List<Class<?>> controlClassRefs = controlClasses.loadClasses();// 找直接子类,而不是子类的子类ClassInfoList directBoxes = scanResult.getSubclasses("com.xyz.Box").directOnly();
}

4、查找类的方法、注解、字段

try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages(Test.class.getPackage().getName()).scan()) {/*** 查找类 com.xyz.Form 的方法、字段和注解* 从一个 ClassInfo 对象中,你可以获取一个 MethodInfoList 的 MethodInfo 对象、一个 FieldInfoList 的 FieldInfo 对象,* 以及/或者一个 AnnotationInfoList 的 AnnotationInfo 对象,它们分别提供关于类的方法、字段和注解的信息。* 同样,这一切都是在不加载或初始化类的情况下完成的。**/ClassInfo form = scanResult.getClassInfo("com.xyz.Form");if (form != null) {MethodInfoList formMethods = form.getMethodInfo();// 方法for (MethodInfo mi : formMethods) {String methodName = mi.getName();MethodParameterInfo[] mpi = mi.getParameterInfo();for (int i = 0; i < mpi.length; i++) {String parameterName = mpi[i].getName();TypeSignature parameterType =mpi[i].getTypeSignatureOrTypeDescriptor();// ...}}// 字段FieldInfoList formFields = form.getFieldInfo();for (FieldInfo fi : formFields) {String fieldName = fi.getName();TypeSignature fieldType = fi.getTypeSignatureOrTypeDescriptor();// ...}// 注解AnnotationInfoList formAnnotations = form.getAnnotationInfo();for (AnnotationInfo ai : formAnnotations) {String annotationName = ai.getName();List<AnnotationParameterValue> annotationParamVals =ai.getParameterValues();// ...}}}

5、使用过滤器+并交集

try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages(Test.class.getPackage().getName()).scan()) {/*** 查找带注解 com.xyz.Checked 的 com.xyz.Box 的子类* ClassInfoList 提供了并集("and")、交集("or")以及集合差集/排除("and-not")运算符:*/ClassInfoList boxes = scanResult.getSubclasses("com.xyz.Box");ClassInfoList checked = scanResult.getClassesWithAnnotation("com.xyz.Checked");ClassInfoList checkedBoxes = boxes.intersect(checked); // 交集// 使用过滤条件同样可以实现,如果是交集的话ClassInfoList checkedBoxes2 = scanResult.getSubclasses("com.xyz.Box").filter(classInfo -> classInfo.hasAnnotation("com.xyz.Checked"));/*** 使用复杂过滤条件*/ClassInfoList filtered = scanResult.getAllClasses().filter(classInfo ->(classInfo.isInterface() || classInfo.isAbstract())&& classInfo.hasAnnotation("com.xyz.Widget")&& classInfo.hasMethod("open"));// 请注意,某些 ClassInfo 谓词方法不接受参数,因此它们也可以直接作为函数引用来代替 ClassInfoFilter 使用,例如:ClassInfoList interfaces = filtered.filter(ClassInfo::isInterface);}

6、读取类型注解

在 Java 中,可以在类型上添加注解(可选带参数)。以下示例打印 100 ,该值是从字段 List<@Size(100) String> values 上的类型参数注解 @Size(100) 中读取的:

public class TypeAnnotation {@Retention(RetentionPolicy.RUNTIME)public @interface Size {int value();}public class Test {List<@Size(100) String> values;}public static void main(String[] args) {try (ScanResult scanResult = new ClassGraph().acceptPackages(TypeAnnotation.class.getPackage().getName()).enableAllInfo().scan()) {ClassInfo ci = scanResult.getClassInfo(Test.class.getName());FieldInfo fi = ci.getFieldInfo().get(0);ClassRefTypeSignature ts = (ClassRefTypeSignature) fi.getTypeSignature();List<TypeArgument> taList = ts.getTypeArguments();TypeArgument ta = taList.get(0);ReferenceTypeSignature taSig = ta.getTypeSignature();AnnotationInfoList aiList = taSig.getTypeAnnotationInfo();AnnotationInfo ai = aiList.get(0);AnnotationParameterValueList apVals = ai.getParameterValues();AnnotationParameterValue apVal = apVals.get(0);int size = (int) apVal.getValue();System.out.println(size);}}
}

7、扫描特定 URL

与其扫描所有检测到的类加载器和模块,您可以通过在 .overrideClassLoaders(new URLClassLoader(urls)) 或直接在 .overrideClasspath(urls) 之前调用 .scan() 来扫描特定的 URL:

public void scan(URL[] urls) {try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("com.xyz").overrideClassLoaders(new URLClassLoader(urls)).scan()) {// ...}
}

或者

public void scan(String pathToScan) {try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("com.xyz").overrideClasspath(pathToScan).scan()) {// ...}
}

8、查找和读取资源文件


import io.github.classgraph.ClassGraph;
import io.github.classgraph.Resource;
import io.github.classgraph.ScanResult;import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;public class Test {public static void main(String[] args) throws Exception {/*** 读取所有 XML 资源文件的内容,位于 META-INF/config 。* 这是一种不同类型的查询,它根据匹配的文件路径查找资源,而不是根据类属性查找类。* 如果你只需要扫描资源而不需要扫描类,为了提高速度,不应调用 .enableClassInfo() 或 .enableAllInfo() 。* 此外,如果你不需要扫描类,应通过调用 .acceptPaths() 并使用路径分隔符( / )来指定接受,而不是通过调用 .acceptPackages() 并使用包分隔符( . )来指定。* 路径和包接受在内部工作方式相同,你可以选择其中一种方式来指定接受/拒绝。* 然而,调用 .acceptPackages() 也会隐式调用 .enableClassInfo() 。*** ScanResult 中有几种方法可以获取符合给定条件的资源:* .getAllResources()* .getResourcesWithPath(String resourcePath)* .getResourcesWithLeafName(String leafName)* .getResourcesWithExtension(String extension)* .getResourcesMatchingPattern(Pattern pattern)*/Map<String, String> pathToFileContent = new HashMap<>();try (ScanResult scanResult = new ClassGraph().acceptPaths("META-INF/config").scan()) {scanResult.getResourcesWithExtension("xml").forEachByteArray((Resource res, byte[] fileContent) -> {pathToFileContent.put(res.getPath(), new String(fileContent, StandardCharsets.UTF_8));});}}
}

9、查找类路径或模块路径中的所有重复类定义

知道同一个类在类路径或模块路径中定义多次时可能很有用。

ScanResult 中,ClassGraph 仅对任何给定的完全限定类名返回一个 ClassInfo 对象,该对象对应于类路径或模块路径中遇到的第一个类实例(为了模拟 JRE 的“遮蔽”或“阴影”语义,同一类的后续定义会被忽略)。然而,ScanResult#getAllResources()返回一个 ResourceList ,其中包含针对非类文件和类文件的 Resource 对象(因为类文件在技术上是一种资源)。

调用 ResourceList#classFilesOnly() 会返回另一个 ResourceList ,其中只包含路径以 ".class" 结尾的 Resource 元素。

调用 ResourceList#findDuplicatePaths() 会返回一个 List<Entry<String, ResourceList>> ,其中条目的键是路径,条目的值是一个 ResourceList ,包含两个或多个 Resource 对象,用于重复的资源。

因此,你可以按照以下方式打印所有重复的 class 文件的类路径/模块路径 URL:

for (Entry<String, ResourceList> dup :new ClassGraph().scan().getAllResources().classFilesOnly()                        // Remove this for all resource types.findDuplicatePaths()) {System.out.println(dup.getKey());                // Classfile pathfor (Resource res : dup.getValue()) {System.out.println(" -> " + res.getURI());   // Print Resource URI}
}

http://www.xdnf.cn/news/16247.html

相关文章:

  • linux C — udp,tcp通信
  • 【Chrome】下载chromedriver的地址
  • 深入解析浏览器存储方案:Cookie、localStorage和sessionStorage特性与应用
  • GPU 服务器ecc报错处理
  • Java排序算法之<冒泡排序>
  • 单片机(STM32-ADC模数转换器)
  • 优思学院|QC七大手法之一的检查表应如何有效使用?
  • CSS 盒子模型学习版的理解
  • 数据结构 二叉树(1)
  • yarn在macOS上的安装与镜像源配置:全方位指南
  • 从 SQL Server 到 KingbaseES V9R4C12,一次“无痛”迁移与深度兼容体验实录
  • Orbbec开发---数据流与数据流操作
  • ZLMediaKit 源代码入门
  • Spring 策略模式实现
  • 【DeepRare】疾病识别召回率100%
  • SpringBoot学习路径二--Spring Boot自动配置原理深度解析
  • 教培机构如何开发自己的证件照拍照采集小程序
  • 萤石云替代产品摄像头方案萤石云不支持TCP本地连接-东方仙盟
  • 深入解析Hadoop MapReduce中Reduce阶段排序的必要性
  • 《Uniapp-Vue 3-TS 实战开发》自定义环形进度条组件
  • 人工智能冗余:大语言模型为何有时表现不佳(以及我们能做些什么)
  • 【js】ES2025新语法糖
  • 缓存HDC内容用于后续Direct2D绘制.
  • C#(基本语法)
  • SQLite中SQL的解析执行:Lemon与VDBE的作用解析
  • 机器学习笔记(三)——决策树、随机森林
  • 使用Python绘制金融数据可视化工具
  • 云原生可观测-日志观测(Loki)最佳实践
  • MinIO:云原生对象存储的终极指南
  • IT领域需要“落霞归雁”思维框架的好处