要实现第二小步(读取转换好的 Resource 中的 @Component 注解),首先面临的第一个问题是:如何读取字节码?,熟悉字节结构的朋友可以字节解析读取,但是难度相对比较大,而且也比较容易出错,这里读取字节码的操作我们使用著名的字节码操作框架 ASM 来完成底层的操作,官网对其的描述入下:
ASM is an all purpose Java bytecode manipulation and analysis framework.
其描述就是:ASM 是一个通用的 Java 字节码操作和分析框架。其实不管是在工作或者日常学习中,我们对于一些比较基础的库和框架,如果有成熟的开源框架使用其实没有从零开发(当然,本身就是想要研究其源码的除外),这样可以减少不必要的开发成本和精力。ASM 基于 Visitor 模式可以方便的读取和修改字节码,目前我们只需要使用其读取字节码的功能。
ASM 框架中分别提供了 ClassVisitor 和 AnnotationVisitor 两个抽象类来访问类和注解的字节码,我们可以使用这两个类来获取类和注解的相关信息。很明显我们需要继承这两个类然后覆盖其中的方法增加自己的逻辑去完成信息的获取,要如何去描述一个类呢?其实比较简单无非就是 类名、是否是接口、是否是抽象类、父类的类名、实现的接口列表 等这几项。
但是一个注解要如何去描述它呢?注解其实我们主要关注注解的类型和其所包含的属性,类型就是一个 包名 + 注解名 的字符串表达式,而属性本质上是一种 K-V 的映射,值类型可能为 数字、布尔、字符串 以及 数组 等,为了方便使用可以继承自 LinkedHashMap<String, Object> 封装一些方便的获取属性值的方法,读取注解部分的相关类图设计如下:
其中绿色背景的 ClassVisitor 和 AnnotationVisitor 是 ASM 框架提供的类,ClassMetadata 是类相关的元数据接口,AnnotationMetadata 是注解相关的元数据接口继承自 ClassMetadata,AnnotationAttributes 是对注解属性的描述,继承自 LinkedHashMap 主要是封装了获取指定类型 value 的方法,还有三个自定义的 Visitor 类是本次实现的关键,第一个类 ClassMetadataReadingVisitor 实现了 ClassVisitor 抽象类,用来获取字节码文件中类相关属性的提取,其代码实现如下所示:
/** * @author mghio * @since 2021-02-14 */ public class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata { private String className; private Boolean isInterface; private Boolean isAbstract; ... public ClassMetadataReadingVisitor() { super(Opcodes.ASM7); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = ClassUtils.convertResourcePathToClassName(name); this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0); this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0); ... } @Override public String getClassName() { return this.className; } @Override public boolean isInterface() { return this.isInterface; } @Override public boolean isAbstract() { return this.isAbstract; } ... }