spring 中的字节码文件访问 -- classreading 包

位于 spring-core 模块下的 org.springframework.core.type.classreading 包提供了读取类中元数据的功能。其实就是在不加载类的情况下,获取 class 文件中定义的类的相关信息:类名、接口、注解、方法及其注解、字段及其注解等。方便 spring 进行类型或指定注解的判断,对符合条件的类才进行加载并进行实例化对象的创建。

下面我们先来看看这个 classreading 包下都有哪些类。

主要类介绍

为方便使用,自 spring 5.2 版本后,废弃了 classreading 包下不少操作类。今天要介绍的这些类都是 classreading 包下正在使用的类,不包含废弃的类。

MetadataReaderFactory

这是一个接口,也是 spring 中获取 MetadataReader 操作的入口,主要功能就是创建、获取 MetadataReader 实例对象。

MetadataReader getMetadataReader(String className) throws IOException;MetadataReader getMetadataReader(Resource resource) throws IOException;

实现类有两个:

  • SimpleMetadataReaderFactory,MetadataReaderFactory 接口的主要实现类,MetadataReader 的创建就在此完成。
  • CachingMetadataReaderFactory,继承 SimpleMetadataReaderFactory, 扩展了缓存创建的 MetadataReader 的功能。
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}

这是创建 MetadataReader 的入口。当传参为 String 类型的 className 时,通过创建 SimpleMetadataReaderFactory 时传入的 resourceLoader,调用 getResource 方法得到 Resource 后,再调用上面的 getMetadataReader 方法创建 SimpleMetadataReader 实例对象。

MetadataReader

这是一个接口,提供了对获取 class 文件元数据信息的简单封装。

Resource getResource();ClassMetadata getClassMetadata();AnnotationMetadata getAnnotationMetadata();

目前实现类只有一个,SimpleMetadataReader,基于 ASM 中的 ClassReader 来实现 class 文件的读取功能,传入的 ClassVisit 为 SimpleAnnotationMetadataReadingVisitor。并且在 SimpleMetadataReader 中实现了对 getClassMetatdata 和 getAnnotationMetadata 的统一,返回的都是 SimpleAnnotationMetadataReadingVisitor 中生成的 SimpleAnnotationMetadata。

SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {// 引入 ClassVisitSimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);// 利用 ClassReader 接收 ClassVisit 访问这个类getClassReader(resource).accept(visitor, PARSING_OPTIONS);this.resource = resource;this.annotationMetadata = visitor.getMetadata();
}

class 文件访问

可以看到,是通过 SimpleAnnotationMetadataReadingVisitor 来对 class 文件进行访问的,SimpleAnnotationMetadataReadingVisitor 继承 ASM 中的 ClassVisit,通过重写 ClassVisit 中的相关方法,实现对 class 文件中类的元数据获取。主要关注以下几个重写的方法:

  • visit: 访问 className,access,superClassName,interfaceNames
  • visitAnnotation: 注解的访问,委托给 MergedAnnotationReadingVisitor 来处理
  • visitMethod: 方法的访问,委托给 SimpleMethodMetadataReadingVisitor 来处理
  • visitEnd: 封装 SimpleAnnotationMetadata,赋值给 metadata 属性            

其中,不管是类上面的注解,还是方法中的注解,最终都是通过 MergedAnnotationReadingVisitor 来访问的;不管是接口还是类,一个方法对应一个 SimpleMethodMetadataReadingVisit 实例对象。

下面我们来看下具体的访问过程:

在 ClassReader 中定义了读取 class 文件中类相关信息的操作流程和方法,之后通过 ClassVisit 进行访问。ClassVisit 是一个抽象类,也就是说,具体的访问操作,由用户自行指定。而 ClassReader 是固定的,按照虚拟机规范中 class 的文件结构,对 class 文件中的各个部分进行读取。

visit

@Override
public void visit(int version, int access, String name, String signature,@Nullable String supername, String[] interfaces) {this.className = toClassName(name);this.access = access;if (supername != null && !isInterface(access)) {this.superClassName = toClassName(supername);}this.interfaceNames = new String[interfaces.length];for (int i = 0; i < interfaces.length; i++) {this.interfaceNames[i] = toClassName(interfaces[i]);}
}

此方法的主要作用,就是将 ClassReader 读取的类相关信息,赋值给 SimpleAnnotationMetadataReadingVisitor 中的相关变量。

visitAnnotation

如果 class 文件定义的类上有注解,会调用此方法:

@Override
@Nullable
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {return MergedAnnotationReadingVisitor.get(this.classLoader, getSource(),descriptor, visible, this.annotations::add);
}

可以看到,注解的访问委托给了 MergedAnnotationReadingVisitor 去操作。

@Nullable
static <A extends Annotation> AnnotationVisitor get(@Nullable ClassLoader classLoader,@Nullable Object source, String descriptor, boolean visible,Consumer<MergedAnnotation<A>> consumer) {// 不可见的,直接返回 nullif (!visible) {return null;}// 举例:org.springframework.stereotype.ComponentString typeName = Type.getType(descriptor).getClassName();if (AnnotationFilter.PLAIN.matches(typeName)) {return null;}try {// 用指定的 classLoader 加载 typeName 表示的注解类型Class<A> annotationType = (Class<A>) ClassUtils.forName(typeName, classLoader);return new MergedAnnotationReadingVisitor<>(classLoader, source, annotationType, consumer);}catch (ClassNotFoundException | LinkageError ex) {return null;}
}

此时还未开始访问注解,只是创建了 AnnotationVisit,供 ClassReader 使用。在 ClassReader#readElementValues 方法中,才去执行注解的访问。之后执行 annotationVisitor.visitEnd() 进入 MergedAnnotationReadingVisitor#visitEnd 中执行。

@Override
public void visitEnd() {MergedAnnotation<A> annotation = MergedAnnotation.of(this.classLoader, this.source, this.annotationType, this.attributes);this.consumer.accept(annotation);
}

创建一个 TypeMappedAnnotation 实例对象,调用 consumer 方法,此时的 consumer 就是创建 MergedAnnotationReadingVisitor 时传入的 lambda 表达式,其实就是将得到的 TypeMappedAnnotation 实例对象加入 annotations 集合,这个 annotations 集合是 SimpleAnnotationMetadataReadingVisitor 中的字段。

需要注意的一点,SimpleAnnotationMetadataReadingVisitor 并未重写 visitField 方法,也就是在此处不需要处理 Field 上的相关注解。

visitMethod

@Override
@Nullable
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {// Skip bridge methods - we're only interested in original// annotation-defining user methods. On JDK 8, we'd otherwise run into// double detection of the same annotated method...if (isBridge(access)) {return null;}return new SimpleMethodMetadataReadingVisitor(this.classLoader, this.className,access, name, descriptor, this.annotatedMethods::add);
}

针对方法的访问,委托给 SimpleMethodMetadataReadingVisitor 来处理,一个方法对应一个 SimpleMethodMetadataReadingVisitor 实例对象。顺便说下,针对类中的 "<init>" 和               "<cinit>" 两个特殊方法,也会创建 SimpleMethodMetadataReadingVisitor 实例对象来访问

方法上如果存在注解,调用 SimpleMethodMetadataReadingVisitor#visitAnnotation。

@Override
@Nullable
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {return MergedAnnotationReadingVisitor.get(this.classLoader, getSource(),descriptor, visible, this.annotations::add);
}

可以看到,对注解的访问,不管是方法注解,还是类注解,都是通过 MergedAnnotationReadingVisitor 作为 AnnotationVisit 来操作的。

待类中所有方法都访问结束后,调用 SimpleMethodMetadataReadingVisitor#visitEnd。

@Override
public void visitEnd() {if (!this.annotations.isEmpty()) {String returnTypeName = Type.getReturnType(this.descriptor).getClassName();MergedAnnotations annotations = MergedAnnotations.of(this.annotations);SimpleMethodMetadata metadata = new SimpleMethodMetadata(this.methodName, this.access,this.declaringClassName, returnTypeName, getSource(), annotations);this.consumer.accept(metadata);}
}

主要是针对方法上注解的操作,封装 SimpleMethodMetadata,填充 SimpleAnnotationMetadataReadingVisitor 中 annotatedMethods 字段。

visitEnd

待 ClassReader#accept 方法执行到最后,会调用 ClassVisit#visitEnd 方法,此处调用 SimpleAnnotationMetadataReadingVisitor#visitEnd。

@Override
public void visitEnd() {// 存在内部类的情况String[] memberClassNames = StringUtils.toStringArray(this.memberClassNames);// 方法上注解MethodMetadata[] annotatedMethods = this.annotatedMethods.toArray(new MethodMetadata[0]);// 类上注解MergedAnnotations annotations = MergedAnnotations.of(this.annotations);// 将访问到的关于类的信息,封装到 SimpleAnnotationMetadata 中this.metadata = new SimpleAnnotationMetadata(this.className, this.access,this.enclosingClassName, this.superClassName, this.independentInnerClass,this.interfaceNames, memberClassNames, annotatedMethods, annotations);
}

将访问到的关于类的信息,最后都封装到了 SimpleAnnotationMetadata 类中,赋值给 SimpleAnnotationMetadataReadingVisitor 中 metadata 字段。

这样就完成了对类的访问。接着回到 SimpleMetadataReader 构造方法中,将 SimpleAnnotationMetadataReadingVisitor 中 metadata 字段返回,并赋值给 SimpleMetadataReader 中 annotationMetadata 字段。

创建完 SimpleMetadataReader 之后,如果使用的工厂是 CachingMetadataReaderFactory,会将这个 MetadataReader 实例对象缓存到 metadataReaderCache,key 为 Resource 实例对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/48163.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客0718B——Arraylist 和LinkedList static修饰变量和方法

目录 Q1:currentTimeMillis是什么&#xff1f; Q2:比较Arraylist 和LinkedList的查找时间 3、相关对比Vector和Arraylist 底层扩容的原理: Q4:static修饰静态代码块 修改1&#xff1a; 修改2&#xff1a; 修改3&#xff1a; 修改1&#xff1a; 修改2&#xff1a; Q5…

入坑树莓派(2)——树莓派4B与手机蓝牙通信

入坑树莓派(2)——树莓派4B与手机蓝牙通信 1、引言 在入坑树莓派(1)中已经搞掂了可视化问题。现在继续开展下一步,尝试与手机通信,一开始是想弄wifi连接的,但发现基于wifi的APP比较难弄,为了降低开发的难度,又因为树莓派板子自带蓝牙模块,所以直接选用蓝牙连接手机…

LabVIEW多串口通信

随着现代工业控制对数据采集和处理效率的要求不断提升&#xff0c;传统的单串口通信已无法满足多通道数据传输与大规模数据存取的需求。开发一种基于LabVIEW的多串口通信及数据存储系统&#xff0c;以提升数据处理速度和存储效率&#xff0c;保障生产线的稳定运行显得尤为重要。…

达梦+flowable改造

原项目springbootflowablemysql模式现需改造springbootflowable达梦&#xff0c; 1.在项目中引入达梦jpa包 引入高版本包已兼容flowable&#xff08;6.4.2&#xff09;liquibase&#xff08;3.6.2&#xff09; 我没有像网上做覆盖及达梦配置 <dependency> …

数据结构之树的存储结构详解与示例(C/C++)

文章目录 树的存储结构1. 顺序存储结构2. 链式存储结构结论 树&#xff08;Tree&#xff09;是一种非常常见的数据结构&#xff0c;它模拟了一种层级或分支结构。树由节点&#xff08;或称为顶点&#xff09;组成&#xff0c;每个节点包含一个值&#xff0c;并且可能有多个子节…

SpringDoc2问题汇总

在项目中尝试使用SpringDoc进行文档生成&#xff0c;在使用过程中遇到一系列的问题加以记录. 1.引入依赖 只是单纯的使用SpringDoc的话不需要引入一些乱七八糟的依赖&#xff0c;如今各种增强和拓展依赖层出不穷&#xff0c;但是随着这些依赖的出现带来的不仅是增强&#xff0…

在学习使用LabVIEW的过程中,需要注意哪些问题?

在学习使用LabVIEW的过程中&#xff0c;需要注意以下问题&#xff1a; 1. 基础知识 图形化编程思维&#xff1a; LabVIEW采用图形化编程方式&#xff0c;与传统的文本编程语言有很大不同&#xff0c;需要适应这种新的编程思维方式。数据流概念&#xff1a; 理解LabVIEW的核心数…

调用第三方接口-OkHttpClient

请求方式 POSTGET POST 单个新增 例如后端接口接收参数为 User user 使用OkHttpClient发送post请求 //封装body信息 JsonObject jsonObject new JsonObject(); jsonObject.put("userName","张三"); jsonObject.put("city","北京");…

服务器借助笔记本热点WIFI上网

一、同一局域网环境 1、当前环境&#xff0c;已有交换机组网环境&#xff0c;服务器已配置IP信息。 设备ip服务器125.10.100.12交换机125.10.100.0/24笔记本125.10.100.39 2、拓扑图 #mermaid-svg-D4moqMym9i0eeRBm {font-family:"trebuchet ms",verdana,arial,sa…

AFAC2024-基于保险条款的问答 比赛日记 llamafactory qwen npu 910B1

AFAC2024: 基于保险条款的问答挑战——我的实战日记 概述 在最近的AFAC2024竞赛中&#xff0c;我参与了基于保险条款的问答赛道。这是一次深度学习与自然语言处理的实战演练&#xff0c;旨在提升模型在复杂保险文本理解与问答生成方面的能力。本文将分享我的参赛过程&#xf…

Git技巧:如何重命名你的分支

0. 引言 本文将介绍如何在本地以及远程仓库中安全地重命名 Git 分支。 1. 在本地重命名分支 在本地重命名分支可以通过 git branch 命令完成&#xff0c;具体有两种方法&#xff1a; 方法1&#xff1a;当前分支重命名 如果你当前正在 old 分支上工作&#xff0c;想要将其重…

numpy的一些基本操作

文章目录 1.numpy数组的多种创建方式1.1使用np.array()创建1.2使用plt创建1.3使用np的routine函数创建 2.numpy的常用属性2.1shape2.2ndim2.3size2.4dtype 3.numpy的索引和切片3.1切出前两列数据3.2切出前两行数据3.3切出前两行的前两列的数据3.4数组数据翻转3.5练习&#xff1…

【权威发布】2024年生物技术与医学国际会议(IACBM 2024)

2024年生物技术与医学国际会议 2024 International Conference on Biotechnology and Medicine 【1】会议简介 2024年生物技术与医学国际会议旨在为全球生物技术与医学领域的专家学者提供一个交流最新研究成果、分享技术进展和探讨未来发展方向的平台。会议旨在加强国际间的学术…

阿里云 https证书部署

一.申请证书 二.查看状态 查看状态&#xff0c;已签发是完成了申请证书 三.部署 我在nginx服务器上部署 具体操作链接:阿里云文档 修改前 修改后 四.重启ngnix 五.验证是否成功 在浏览器输入域名查看

vue2关于Object.defineProperty实现响应式

实现步骤&#xff1a; 1. 初始化阶段 当 Vue 实例化时&#xff0c;会遍历data 选项中的属性&#xff0c;并使用 Object.defineProperty 将它们转换为 getter 和 setter。这样一来&#xff0c;每当访问或修改这些属性时&#xff0c; Vue就能捕获到这些操作&#xff0c;从而实现…

【JavaScript 算法】最长公共子序列:字符串问题的经典解法

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、算法原理状态转移方程初始条件 二、算法实现注释说明&#xff1a; 三、应用场景四、总结 最长公共子序列&#xff08;Longest Common Subsequence&#xff0c;LCS&#xff09;是字符串处理中的经典问题。给定两个字符串…

ETL电商项目总结

ETL电商项目总结 ETL电商业务简介及各数据表关系 业务背景 ​ 本案例围绕某个互联网小型电商的订单业务来开发。某电商公司&#xff0c;每天都有一些的用户会在线上采购商品&#xff0c;该电商公司想通过数据分析&#xff0c;查看每一天的电商经营情况。例如&#xff1a;电商…

【React Hooks原理 - useSyncExternalStore】

概述 在React项目中说到状态管理&#xff0c;我们第一时间想到的就是使用useState、useReducer这种Hooks来进行状态管理。但是这种是针对React内部的状态&#xff0c;如果有时候我们需要订阅外部的状态并影响React组件的更新的话&#xff0c;那通过这种内部状态管理API显然不能…

通信协议_C#实现CAN通信

CAN协议 CAN&#xff08;Controller Area Network&#xff09;即控制器局域网络。特点&#xff1a; 多主网络&#xff1a;网络上的任何节点都可以主动发送数据&#xff0c;不需要一个固定的主节点。双绞线&#xff1a;使用双绞线作为通信介质&#xff0c;支持较远的通信距离。…

时序数据库如何选型?详细指标总结!

工业物联网场景&#xff0c;如何判断什么才是好的时序数据库&#xff1f; 工业物联网将机器设备、控制系统与信息系统、业务过程连接起来&#xff0c;利用海量数据进行分析决策&#xff0c;是智能制造的基础设施&#xff0c;并影响整个工业价值链。工业物联网机器设备感知形成了…