深入探索编译插桩技术(四、ASM 探秘,二本学渣考研失败
目录
1、ASM 的优势
2、ASM 的逆势
1、优点
2、缺点
3、获取节点
4、操控操作码
2、类读取(解析)者 ClassVisitor
3、小结
四、综合实战训练
1、使用 ASM Bytecode Outline
2、使用 ASM 编译插桩统计方法耗时
3、全局替换项目中所有的 new Thread
五、总结
一、ASM 的优势和逆势
使用 ASM 操作字节码的优势与逆势都 比较明显,其分别如下所示。
1、ASM 的优势
1)、内存占用很小。
2)、运行速度非常快。
3)、操作灵活:对于字节码的操作非常地灵活,可以进行插入、删除、修改等操作。
4)、想象空间大,能够借用它提升生产力。
5)、丰富的文档与众多社区的支持。
2、ASM 的逆势
上手难度较大,需要对 Java 字节码有比较充分的了解。
对于 ASM 而言,它提供了 两种模型:对象模型和事件模型。
下面,我们就先来讲讲 ASM 的对象模型。
二、ASM 的对象模型(ASM Tree API)
对象模型的 本质 是一个 被封装过后的事件模型,它 使用了树状图的形式来描述一个类,其中包含多个节点,例如方法节点、字段节点等等,而每个节点又有子节点,例如方法节中有操作码子节点 等等。下面我们先来了解下由这种树状图模式实现的对象模型的利弊。
1、优点
1)、适宜处理简单类的修改。
2)、学习成本较低。
3)、代码量较少。
2、缺点
1)、处理大量信息会使代码变得复杂。
2)、代码难以复用。
在对象模型下的 ASM 有 两类操作纬度,分别如下所示:
1)、
获取节点
:获取指定类、字段、方法节点。2)、
操控操作码(针对方法节点)
:获取操作码位置、替换、删除、插入操作码、输出字节码。
下面我们就分别来了解下 ASM 的这两类操作。
3、获取节点
1)、获取指定类的节点
获取一个类节点的代码如下所示:
ClassNode classNode = new ClassNode();// 1ClassReader classReader = new ClassReader(bytes);// 2classReader.accept(classNode, 0);
在注释 1 处,将字节数组传入一个新创建的 ClassReader,这时 ASM 会使用 ClassReader 来解析字节码。接着,在注释 2 处,ClassReader 在解析完字节码之后便可以通过 accept 方法来将结果写入到一个 ClassNode 对象之中。
那么,一个 ClassNode 具体又包含哪些信息呢?
如下所示:
类节点信息
2)、获取指定字段的节点
获取一个字段节点的代码如下所示:
for(FieldNode fieldNode : (List)classNode.fields) {// 1if(fieldNode.name.equals("password")) {// 2fieldNode.access = Opcodes.ACC_PUBLIC;}}
字段节点列表 fields 是一个 ArrayList,它储存着类节点的所有字段。在注释 1 处,我们通过遍历 fields 集合的方式来找到目标字段节点。接着,在注释 2 处,我们将目标字段节点的访问权限置为 public。
除此之外,我们还可以为类添加需要的字段,代码如下所示:
FieldNode fieldNode = new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "JsonChao", "I", null, null);classNode.fields.add(fieldNode);
在上述代码中,我们直接给目标类节点添加了一个 "public static int JsonChao" 的字段,需要注意的是,第三个参数的 "I" 表示的是 int 的类型描述符。
那么,对于一个字段节点,又包含有哪些字段信息呢?
如下所示:
字段信息
接下来,我们看看如何获取一个方法节点。
3)、获取指定的方法节点
获取指定的方法节点的代码如下所示:
for(MethodNode methodNode : (List)classNode.methods) {// 1、判断方法名是否匹配目标方法 if(methodNode.name.equals("getName")) {// 2、进行操作}}
methods 同 fields 一样,也是一个 ArrayList,通过遍历并判断方法名的方式即可匹配到目标方法。
对于一个方法节点来说,它包含有如下信息:
方法节点包含的信息
4、操控操作码
在操控字节码之前,我们必须先了解下 instructions
,即 操作码列表,它是 方法节点中用于存储操作码的地方,其中 每一个元素都代表一行操作码。
ASM 将一行字节码封装为一个 xxxInsnNode(Insn 表示的是 Instruction 的缩写,即指令/操作码),例如 ALOAD/ARestore 指令被封装入变量操作码节点 VarInsnNode,INVOKEVIRTUAL 指令则会被封入方法操作码节点 MethodInsnNode 之中。
对于所有的指令节点 xxxInsnNode 来说,它们都继承自抽象操作码节点 AbstractInsnNode
。其所有的派生类使用详情如下所示。
所有的指令码节点说明
String name 字段的名称
String desc 字段的类型 || FrameNode | 栈映射帧的对应的帧节点 | 待补充 || IincInsnNode | 用于 IINC 变量自加操作的字节码 | int var:目标局部变量的位置
int incr: 要增加的数
|| InsnNode | 一切无参数值操作的字节码,例如 ALOAD_0,DUP(注意不包含 POP) | 无 || IntInsnNode | 用于 BIPUSH、SIPUSH 和 NEWARRAY 这三个直接操作整数的操作 | int operand:操作的整数值 || InvokeDynamicInsnNode | 用于 Java7 新增的 INVOKEDYNAMIC 操作的字节码 | String name:方法名称
String desc:方法描述
Handle bsm:句柄
Object[] bsmArgs:参数常量 || JumpInsnNode | 用于 IFEQ 或 GOTO 等跳转操作字节码 | LabelNode lable:目标 lable || LabelNode | 一个用于表示跳转点的 Label 节点 | 无 || LdcInsnNode | 使用 LDC 加载常量池中的引用值并进行插入的字节码 | Object cst:引用值 || LineNumberNode | 表示行号的节点 | int line:行号
LabelNode start:对应的第一个 Label || LookupSwitchInsnNode | 用于实现 LOOKUPSWITCH 操作的字节码 | LabelNode dflt:default 块对应的 Lable
List keys 键列表
List labels:对应的 Label 节点列表 || MethodInsnNode | 用于 INVOKEVIRTUAL 等传统方法调用操作的字节码,不适用于 Java7 新增的 INVOKEDYNAMIC | String owner :方法所在的类
String name :方法名称
String desc:方法描述 || MultiANewArrayInsnNode | 用于 MULTIANEWARRAY 操作的字节码 | String desc:类型描述
int dims:维数 || TableSwitchInsnNode | 用于实现 TABLESWITCH 操作的字节码 | int min:键的最小值
int max:键的最大值
LabelNode dflt:default 块对应的 Lable
List labels:对应的 Label 节点列表 || TypeInsnNode | 用于实现 NEW、ANEWARRAY 和 CHECKCAST 等类型相关操作的字节码 | String desc:类型
|| VarInsnNode | 用于实现 ALOAD、ASTORE 等局部变量操作的字节码 | int var:局部变量 |
下面,我们就开始来讲解下字节码操控有哪几种常见的方式。
1、获取操作码的位置
获取指定操作码位置的代码如下所示:
for(AbstractInsnNode ainNode : methodNode.instructions.toArray()) {if(ainNode.getOpcode() == Opcodes.SIPUSH && ((IntInsnNode)ainNode).operand == 16) {....//进行操作}}
由于一般情况下我们都无法确定操作码在列表中的具体位置,因此 通常会通过遍历的方式去判断其关键特征,以此来定位指定的操作码,上述代码就能定位到一个 SIPUSH 16 的字节码,需要注意的是,有时一个方法中会有多个相同的指令,这是我们需要靠判断前后字节码识别其特征来定位,也可以记下其命中次数然后设定在某一次进行操作,一般情况下我们都是使用的第二种。
2、替换指定的操作码
替换指定的操作码的代码如下所示:
for(AbstractInsnNode ainNode : methodNode.instructions.toArray()) {if(ainNode.getOpcode() == Opcodes.BIPUSH && ((IntInsnNode)ainNode).operand == 16) {methodNode.instructions.set(ainNode, new VarInsnNode(Opcodes.ALOAD, 1));}}
这里我们 直接调用了 InsnList 的 set 方法就能替换指定的操作码对象,我们在获取了 "BIPUSH 64" 字节码的位置后,便将封装它的操作码替换为一个新的 VarInsnNode 操作码,这个新操作码封装了 "ALOAD 1" 字节码, 将原程序中 将值设为16
替换为 将值设为局部变量1
。
3、删除指定的操作码
methodNode.instructions.remove(xxx);
xxx 表示的是要删除的操作码实例,我们直接调用用 InsnList 的 remove 方法将它移除掉即可。
4、插入指定的操作码
InsnList 主要提供了 四类 方法用于插入字节码,如下所示:
1)、
add(AbstractInsnNode insn)
: 将一个操作码添加到 InsnList 的末尾。2)、
insert(AbstractInsnNode insn)
: 将一个操作码插入到这个 InsnList 的开头。3)、
insert(AbstractInsnNode insnNode,AbstractInsnNode insn)
: 将一个操作码插入到另一个操作码的下面。4)、
insertBefore(AbstractInsnNode insnNode,AbstractInsnNode insn)
将一个操作码插入到另一个操作码的上面
接下来看看如何使用这些方法插入指定的操作码,代码如下所示:
for(AbstractInsnNode ainNode : methodNode.instructions.toArray()) {if(ainNode.getOpcode() == Opcodes.BIPUSH && ((IntInsnNode)ainNode).operand == 16) {methodNode.instructions.insert(a
inNode, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/awt/image/BufferedImage", "getWidth", "(Ljava/awt/image/ImageObserver;)I"));methodNode.instructions.insert(ainNode, new InsnNode(Opcodes.ACONSTNULL));methodNode.instructions.set(ainNode, new VarInsnNode(Opcodes.ALOAD, 1));}}
这样,我们就能将
BIPUSH 16
替换为
ALOAD 1ACONSTNULLINVOKEVIRTUAL java/awt/image/BufferedImage.getWidth(Ljava/awt/image/ImageObserver;)I
当我们操控完指定的类节点之后,就可以使用 ASM 的 ClassWriter 类来输出字节码,代码如下所示:
// 1、让 ClassWriter 自行计算最大栈深度和栈映射帧等信息 ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTEFRAMES);classNode.accept(classWriter);return classWriter.toByteArray();
关于 ClassWriter 的具体用法,我们会在 ASM Core API 这部分来进行逐步讲解。下面??,我们就先来看看 ASM 的事件模型。
三、ASM 的事件模型(ASM Core API)
对象模型是由事件模型封装而成,因此事件模型的上手难度会更大一些。
对于事件模型来说,它 采用了设计模式中的访问者模式。它的出现是为了更好地解决这样一种需求:有 A 个元素和 N 种算法,每个算法都能作用于任意一个元素,并且在不同的元素上有不同的运行方式。
在访问者模式出现之前,我们通常会在每一个元素对应的类中添加 N 个方法,然后在每一个方法中去实现一个算法,但是,这样的做法容易导致代码耦合性过高,并且可维护性差。
因此,访问者模式应运而生,我们可以 建立 N 个访问者,并且每一个访问者拥有一个算法及其内部的 A 种运行方式。当我们需要调用一个算法时,就让相应的访问者去访问元素,然后让访问者根据被访问对象选择相应的算法。
需要注意的是,访问者并没有直接去操作元素,而是先让元素类调用 accept 方法接收访问者,然后,访问者在元素类的内部方法中开始调用 visit 方法访问当前的元素类。这样,访问者便能直接访问元素类中的内部私有成员,其优势在于 避免了暴露不必要的内部细节。
要理解 ASM 的事件模型,我们就需要对其中的 两个重要成员的工作原理 有较深的了解。它们便是 类访问者 ClassVisitor 与 类读取(解析)者 ClassReader。
从字节码的视角中,一个 Java 类由很多组件凝聚而成,而这之中便包括超类、接口、属性、域和方法等等。当我们在使用 ASM 进行操控时,可以将它们视为一个个与之对应的事件。因此 ASM 提供了一个 类访问者 ClassVisitor,以通过它来访问当前类的各个组件,当解析器 ClassReader 依次遇到上述的各个组件时,ClassVisitor 上对应的 visitor 事件处理器方法均会被一一调用。
与类相似,方法也是由多个组件凝聚而成的,其对应着方法属性、注解及编译后的代码(Class 字节码)。ASM 的 MethodVisitor 提供了一种 hook(钩子)机制,以便能够访问方法中的每一个操作码,这样我们便能够对字节码文件进行细粒度地修改。
下面,我们便来一一分析下它们。
1、类访问者 ClassVisitor
通常我们在使用 ASM 的访问者模式有一个模板代码,如下所示:
InputStream is = new FileInputStream(classFile);// 1ClassReader classReader = new ClassReader(is);// 2ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);// 3ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);// 4classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
首先,在注释 1 处,我们 将目标文件转换为流的形式,并将它融入类读取器 ClassReader 之中。然后,在注释 2 处,我们 构建了一个类写入器 ClassWriter,其参数 COMPUTE_MAXS 的作用是将自动计算本地变量表最大值和操作数栈最大值的任务托付给了 ASM。接着,在注释 3 处,新建了一个自定义的类访问器,这个自定义的 ClassVisitor
的作用是为了在每一个方法的开始和结尾处插入相应的记时代码,以便统计出每一个方法的耗时。最后,在注释 4 处,类读取器 ClassReader 实例这个被访问者调用了自身的 accept 方法接收了一个 classVisitor 实例,需要注意的是,第二个参数指定了 EXPAND_FRAMES
,旨在说明在读取 class 的时候需要同时展开栈映射帧(StackMap Frame),如果我们需要使用自定义的 MethodVisitor 去修改方法中的指令时必须要指定这个参数,。
上面,我们说到了栈映射帧(StackMap Frame),它到底是什么呢?
栈映射帧 StackMap Frame
它是 Java 6 以后引入的一种验证机制,用于 检验 Java 字节码的正确性。它的工作方式是 记录每一个关键步骤完成后其方法中操作数栈的理论状态,然后,在实际运行的时候,ASM 会将其实际状态和理论状态对比,如果状态不一致则表明出现了错误。
但栈映射帧的实现并不简单,因此通过调用 classReader 实例的 accept 方法我们便可以让 ASM 自动去计算栈映射帧,尽管这 会增加 50% 的额外运算。此外,可能会有小概率的情况遇到 栈映射帧验证失败 的情况,例如:VerifyError: Inconsistent stackmap frames at branch target
这个错误。
最常见的原因可能就是由于 字节码写错造成的,此时,我们应该去检查对应的字节码实现代码。此外,也可能是 JDK 版本的支持问题或是 ASM 自身的缺陷,但是,这种情况几乎不会发生。
2、类读取(解析)者 ClassVisitor
现在,让我们再回到上述注释 4 处的代码,在这里,我们调用了 classReader 的 accept 方法接收了一个访问者 classVisitor,下面,我们来看看其内部的实现,代码如下所示(源码实现较长,这里我们只需关注注释处的代码即可:
/**
Makes the given visitor visit the Java class of this {@link ClassReader}
. This class is the one specified in the constructor (see
{@link #ClassReader(byte[]) ClassReader}).
@param classVisitor
@param flags
*/public void accept(final ClassVisitor classVisitor, final int flags) {accept(classVisitor, new Attribute[0], flags);}
在 accept 方法中又继续调用了 classReader 的另一个 accept 重载方法,如下所示:
public void accept(final ClassVisitor classVisitor,final Attribute[] attrs, final int flags) {int u = header; // current offset in the class filechar[] c = new char[maxStringLength]; // buffer used to read strings
Context context = new Context();context.attrs = attrs;context.flags = flags;context.buffer = c;
// 1、读取类的描述信息,例如 access、name 等等 int access = readUnsignedShort(u);String name = readClass(u + 2, c);String superClass = readClass(u + 4, c);String[] interfaces = new String[readUnsignedShort(u + 6)];u += 8;for (int i = 0; i < interfaces.length; ++i) {interfaces[i] = readClass(u, c);u += 2;}
// 2、读取类的属性信息,例如签名 signature、sourceFile 等等。String signature = null;String sourceFile = null;String sourceDebug = null;String enclosingOwner = null;String enclosingName = null;String enclosingDesc = null;int anns = 0;int ianns = 0;int tanns = 0;int itanns = 0;int innerClasses = 0;Attribute attributes = null;
u = getAttributes();for (int i = readUnsignedShort(u); i > 0; --i) {String attrName = readUTF8(u + 2, c);// tests are sorted in decreasing frequency order// (based on frequencies observed on typical classes)if ("SourceFile".equals(attrName)) {sourceFile = readUTF8(u + 8, c);} else if ("InnerClasses".equals(attrName)) {innerClasses = u + 8;} else if ("EnclosingMethod".equals(attrName)) {enclosingOwner = readClass(u + 8, c);int item = readUnsignedShort(u + 10);if (item != 0) {enclosingName = readUTF8(items[item], c);enclosingDesc = readUTF8(items[item] + 2, c);}} else if (SIGNATURES && "Signature".equals(attrName)) {signature = readUTF8(u + 8, c);} else if (ANNOTATIONS&& "RuntimeVisibleAnnotations".equals(attrName)) {anns = u + 8;} else if (ANNOTATIONS&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {tanns = u + 8;} else if ("Deprecated".equals(attrName)) {access |= Opcodes.ACC_DEPRECATED;} else if ("Synthetic".equals(attrName)) {access |= Opcodes.ACC_SYNTHETIC| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;} else if ("SourceDebugExtension".equals(attrName)) {int len = readInt(u + 4);sourceDebug = readUTF(u + 8, len, new char[len]);} else if (ANNOTATIONS&& "RuntimeInvisibleAnnotations".equals(attrName)) {ianns = u + 8;} else if (ANNOTATIONS&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {itanns = u + 8;} else if ("BootstrapMethods".equals(attrName)) {int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {bootstrapMethods[j] = v;v += 2 + readUnsignedShort(v + 2) << 1;}context.bootstrapMethods = bootstrapMethods;} else {Attribute attr = readAttribute(attrs, attrName, u + 8,readInt(u + 4), c, -1, null);if (attr != null) {attr.next = attributes;attributes = attr;}}u += 6 + readInt(u + 4);}
// 3、访问类的描述信息 classVisitor.visit(readInt(items[1] - 7), access, name, signature,superClass, interfaces);
// 4、访问源码和 debug 信息 if ((flags & SKIP_DEBUG) == 0&& (sourceFile != null || sourceDebug != null)) {classVisitor.visitSource(sourceFile, sourceDebug);}
// 5、访问外部类 if (enclosingOwner != null) {classVisitor.visitOuterClass(enclosingOwner, enclosingName,enclosingDesc);}
// 6、访问类注解和类型注解 if (ANNOTATIONS && anns != 0) {for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {v = readAnnotationValues(v + 2, c, true,classVisitor.visitAnnotation(readUTF8(v, c), true));}}if (ANNOTATIONS && ianns != 0) {for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {v = readAnnotationValues(v + 2, c, true,classVisitor.visitAnnotation(readUTF8(v, c), false));}}if (ANNOTATIONS && tanns != 0) {for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {v = readAnnotationTarget(context, v);v = readAnnotationValues(v + 2, c, true,classVisitor.visitTypeAnnotation(context.typeRef,context.typePath, readUTF8(v, c), true));}}if (ANNOTATIONS && itanns != 0) {for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {v = readAnnotationTarget(context, v);v = readAnnotationValues(v + 2, c, true,classVisitor.visitTypeAnnotation(context.typeRef,context.typePath, readUTF8(v, c), false));}}
// 7、访问类的属性 while (attributes != null) {Attribute attr = attributes.next;attributes.next = null;classVisitor.visitAttribute(attributes);attributes = attr;}
// 8、访问内部类 if (innerClasses != 0) {int v = innerClasses + 2;for (int i = readUnsignedShort(innerClasses); i > 0; --i) {classVisitor.visitInnerClass(readClass(v, c),readClass(v + 2, c), readUTF8(v + 4, c),readUnsignedShort(v + 6));v += 8;}}
// 9、访问字段和方法 u = header + 10 + 2 * interfaces.length;for (int i = readUnsignedShort(u - 2); i > 0; --i) {u = readField(classVisitor, context, u);}u += 2;for (int i = readUnsignedShort(u - 2); i > 0; --i) {u = readMethod(classVisitor, context, u);}
// 访问当前类结束时调用 classVisitor.visitEnd();}
首先,在 classReader 实例的 accept 方法中的注释 1 和注释 2 处,我们会 先开始进行类相关的字节码解析的工作:读取了类的描述和属性信息。接着,在注释 3 ~ 注释 8 处,我们调用了 classVisitor 一系列的 visitxxx 方法访问 classReader 解析完字节码后保存在内存的信息。然后,在注释 9 处,分别调用了 readField 方法和 readMethod 方法去访问类中的方法和字段。最后,调用 classVisitor 的 visitEnd 标识已访问结束。
1)、类内字段的解析
这里,我们先来看看 readField
的源码实现,如下所示:
/**
Reads a field and makes the given visitor visit it.
@param classVisitor
@param context
@param u
@return the offset of the first byte following the field in the class.*/private int readField(final ClassVisitor classVisitor,final Context context, int u) {// 1、读取字段的描述信息 char[] c = context.buffer;int access = readUnsignedShort(u);String name = readUTF8(u + 2, c);String desc = readUTF8(u + 4, c);u += 6;
// 2、读取字段的属性 String signature = null;int anns = 0;int ianns = 0;int tanns = 0;int itanns = 0;Object value = null;Attribute attributes = null;
for (int i = readUnsignedShort(u); i > 0; --i) {String attrName = readUTF8(u + 2, c);// tests are sorted in decreasing frequency order// (based on frequencies observed on typical classes)if ("ConstantValue".equals(attrName)) {int item = readUnsignedShort(u + 8);value = item == 0 ? null : readConst(item, c);} else if (SIGNATURES && "Signature".equals(attrName)) {signature = readUTF8(u + 8, c);} else if ("Deprecated".equals(attrName)) {access |= Opcodes.ACC_DEPRECATED;} else if ("Synthetic".equals(attrName)) {access |= Opcodes.ACC_SYNTHETIC| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;} else if (ANNOTATIONS&& "RuntimeVisibleAnnotations".equals(attrName)) {anns = u + 8;} else if (ANNOTATIONS&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {tanns = u + 8;} else if (ANNOTATIONS&& "RuntimeInvisibleAnnotations".equals(attrName)) {ianns = u + 8;} else if (ANNOTATIONS&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {itanns = u + 8;} else {Attribute attr = readAttribute(context.attrs, attrName, u + 8,readInt(u + 4), c, -1, null);if (attr != null) {attr.next = attributes;attributes = attr;}}u += 6 + readInt(u + 4);}u += 2;
// 3、访问字段的声明 FieldVisitor fv = classVisitor.visitField(access, name, desc,signature, value);if (fv == null) {return u;}
// 4、访问字段的注解和类型注解 if (ANNOTATIONS && anns != 0) {for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {v = readAnnotationValues(v + 2, c, true,fv.visitAnnotation(readUTF8(v, c), true));}}if (ANNOTATIONS && ianns != 0) {for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {v = readAnnotationValues(v + 2, c, true,fv.visitAnnotation(readUTF8(v, c), false));}}if (ANNOTATIONS && tanns != 0) {for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {v = readAnnotationTarget(context, v);v = readAnnotationValues(v + 2, c, true,fv.visitTypeAnnotation(context.typeRef,context.typePath, readUTF8(v, c), true));}}if (ANNOTATIONS && itanns != 0) {for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {v = readAnnotationTarget(context, v);v = readAnnotationValues(v + 2, c, true,fv.visitTypeAnnotation(context.typeRef,context.typePath, readUTF8(v, c), false));}}
// 5、访问字段的属性 while (attributes != null) {Attribute attr = attributes.next;attributes.next = null;fv.visitAttribute(attributes);attributes = attr;}
// 访问字段结束时调用 fv.visitEnd();
return u;}
同读取类信息的时候类似,首先,在注释 1 和注释 2 处,会 先开始进行字段相关的字节码解析的工作:读取了字段的描述和属性信息。然后,在注释 3 ~ 注释 5 处 按顺序访问了字段的描述、注解、类型注解及其属性信息。最后,调用了 FieldVisitor 实例的 visitEnd 方法结束了字段信息的访问。
2)、类内方法的解析
下面,我们在看看 readMethod
的实现代码,如下所示:
/**
Reads a method and makes the given visitor visit it.
@param classVisitor
@param context
@param u
@return the offset of the first byte following the method in the class.*/
评论