ASM初探

对于 ASM 而言,它提供了 两种模型:对象模型和事件模型。

ASM 的对象模型(ASM Tree API)

对象模型的 本质 是一个 被封装过后的事件模型,它 使用了树状图的形式来描述一个类,其中包含多个节点,例如方法节点、字段节点等等,而每个节点又有子节点,例如方法节中有操作码子节点 等等。

在对象模型下的 ASM 有 两类操作纬度,分别如下所示:

1)、获取节点:获取指定类、字段、方法节点。
2)、操控操作码(针对方法节点):获取操作码位置、替换、删除、插入操作码、输出字节码。

1.获取节点
获取一个类节点的代码如下所示:

   ClassNode classNode = new ClassNode();
   // 1
   ClassReader classReader = new ClassReader(bytes);
   // 2
   classReader.accept(classNode, 0);

在注释1处,将字节数组传入一个新创建的 ClassReader,这时 ASM 会使用 ClassReader 来解析字节码。接着,在注释2处,ClassReader 在解析完字节码之后便可以通过 accept 方法来将结果写入到一个 ClassNode 对象之中。

2.获取指定字段的节点

    for(FieldNode fieldNode : (List)classNode.fields) {
        // 1
        if(fieldNode.name.equals("password"))  {
            // 2
            fieldNode.access = Opcodes.ACC_PUBLIC;
        }
    }

字段节点列表 fields 是一个 ArrayList,它储存着类节点的所有字段。在注释1处,我们通过遍历 fields 集合的方式来找到目标字段节点。接着,在注释2处,我们将目标字段节点的访问权限置为 public。

我们还可以为类添加需要的字段,代码如下所示:

    FieldNode fieldNode = new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "Test", "I", null, null);
    classNode.fields.add(fieldNode);

我们直接给目标类节点添加了一个 “public static int Test” 的字段,需要注意的是,第三个参数的 “I” 表示的是 int 的类型描述符。

一个FieldNode包含如下信息:
在这里插入图片描述
3.获取指定的方法节点
获取指定的方法节点的代码如下所示:

    for(MethodNode methodNode : (List)classNode.methods) {
        // 1、判断方法名是否匹配目标方法
        if(methodNode.name.equals("getName")) {
            // 2、进行操作
        }
    }

methods 同 fields 一样,也是一个 ArrayList,通过遍历并判断方法名的方式即可匹配到目标方法。

3.操控操作码
instructions,即 操作码列表,它是 方法节点中用于存储操作码的地方,其中 每一个元素都代表一行操作码。

ASM 将一行字节码封装为一个 xxxInsnNode(Insn 表示的是 Instruction 的缩写,即指令/操作码),例如 ALOAD/ARestore 指令被封装入变量操作码节点 VarInsnNode,INVOKEVIRTUAL 指令则会被封入方法操作码节点 MethodInsnNode 之中。

对于所有的指令节点 xxxInsnNode 来说,它们都继承自抽象操作码节点 AbstractInsnNode。

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 16” 字节码的位置后,便将封装它的操作码替换为一个新的 VarInsnNode 操作码,这个新操作码封装了 “ALOAD 1” 字节码, 将原程序中 将值设为16 替换为 将值设为局部变量1。

3、删除指定的操作码

    methodNode.instructions.remove(xxx);

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(ainNode, 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
```java

替换为

```java
    ALOAD 1
    ACONSTNULL
    INVOKEVIRTUAL java/awt/image/BufferedImage.getWidth(Ljava/awt/image/ImageObserver;)I
```java

**当我们操控完指定的类节点之后,就可以使用 ASM 的 ClassWriter 类来输出字节码**,代码如下所示:

```java
    // 1、让 ClassWriter 自行计算最大栈深度和栈映射帧等信息
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTEFRAMES);
    classNode.accept(classWriter);
    return classWriter.toByteArray();

ASM 的事件模型(ASM Core API)

它 采用了设计模式中的访问者模式。它的出现是为了更好地解决这样一种需求:有 A 个元素和 N 种算法,每个算法都能作用于任意一个元素,并且在不同的元素上有不同的运行方式。

访问者并没有直接去操作元素,而是先让元素类调用 accept 方法接收访问者,然后,访问者在元素类的内部方法中开始调用 visit 方法访问当前的元素类。这样,访问者便能直接访问元素类中的内部私有成员,其优势在于 避免了暴露不必要的内部细节。

从字节码的视角中,一个 Java 类由很多组件凝聚而成,而这之中便包括超类、接口、属性、域和方法等等。当我们在使用 ASM 进行操控时,可以将它们视为一个个与之对应的事件。因此 ASM 提供了一个 类访问者 ClassVisitor,以通过它来访问当前类的各个组件,当解析器 ClassReader 依次遇到上述的各个组件时,ClassVisitor 上对应的 visitor 事件处理器方法均会被一一调用。

方法也是由多个组件凝聚而成的,其对应着方法属性、注解及编译后的代码(Class 字节码)。ASM 的 MethodVisitor 提供了一种 hook(钩子)机制,以便能够访问方法中的每一个操作码,这样我们便能够对字节码文件进行细粒度地修改。

类访问者 ClassVisitor

ASM 的访问者模式有一个模板代码,如下所示:

    InputStream is = new FileInputStream(classFile);
    // 1
    ClassReader classReader = new ClassReader(is);
    // 2
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    // 3
    ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
    // 4
    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);

在注释2处,我们 构建了一个类写入器 ClassWriter,其参数 COMPUTE_MAXS 的作用是将自动计算本地变量表最大值和操作数栈最大值的任务托付给了ASM。

注释4处,类读取器 ClassReader 实例这个被访问者调用了自身的 accept 方法接收了一个 classVisitor 实例,需要注意的是,第二个参数指定了 EXPAND_FRAMES,旨在说明在读取 class 的时候需要同时展开栈映射帧(StackMap Frame),如果我们需要使用自定义的 MethodVisitor 去修改方法中的指令时必须要指定这个参数。

栈映射帧 StackMap Frame
它是 Java 6 以后引入的一种验证机制,用于 检验 Java 字节码的正确性。它的工作方式是 记录每一个关键步骤完成后其方法中操作数栈的理论状态,然后,在实际运行的时候,ASM 会将其实际状态和理论状态对比,如果状态不一致则表明出现了错误。
但栈映射帧的实现并不简单,因此通过调用 classReader 实例的 accept 方法我们便可以让 ASM 自动去计算栈映射帧,尽管这 会增加 50% 的额外运算。此外,可能会有小概率的情况遇到 栈映射帧验证失败 的情况,例如:VerifyError: Inconsistent stackmap frames at branch target 这个错误。
最常见的原因可能就是由于 字节码写错造成的

类读取(解析)者 ClassVisitor
我们调用了 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
     *            the visitor that must visit this class.
     * @param flags
     *            option flags that can be used to modify the default behavior
     *            of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
     *            , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
     */
    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 file
        char[] 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
     *            the visitor that must visit the field.
     * @param context
     *            information about the class being parsed.
     * @param u
     *            the start offset of the field in the class file.
     * @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
     *            the visitor that must visit the method.
     * @param context
     *            information about the class being parsed.
     * @param u
     *            the start offset of the method in the class file.
     * @return the offset of the first byte following the method in the class.
     */
    private int readMethod(final ClassVisitor classVisitor,
            final Context context, int u) {
        // 1、读取方法描述信息
        char[] c = context.buffer;
        context.access = readUnsignedShort(u);
        context.name = readUTF8(u + 2, c);
        context.desc = readUTF8(u + 4, c);
        u += 6;

        // 2、读取方法属性信息
        int code = 0;
        int exception = 0;
        String[] exceptions = null;
        String signature = null;
        int methodParameters = 0;
        int anns = 0;
        int ianns = 0;
        int tanns = 0;
        int itanns = 0;
        int dann = 0;
        int mpanns = 0;
        int impanns = 0;
        int firstAttribute = u;
        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 ("Code".equals(attrName)) {
                if ((context.flags & SKIP_CODE) == 0) {
                    code = u + 8;
                }
            } else if ("Exceptions".equals(attrName)) {
                exceptions = new String[readUnsignedShort(u + 8)];
                exception = u + 10;
                for (int j = 0; j < exceptions.length; ++j) {
                    exceptions[j] = readClass(exception, c);
                    exception += 2;
                }
            } else if (SIGNATURES && "Signature".equals(attrName)) {
                signature = readUTF8(u + 8, c);
            } else if ("Deprecated".equals(attrName)) {
                context.access |= Opcodes.ACC_DEPRECATED;
            } else if (ANNOTATIONS
                    && "RuntimeVisibleAnnotations".equals(attrName)) {
                anns = u + 8;
            } else if (ANNOTATIONS
                    && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
                tanns = u + 8;
            } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
                dann = u + 8;
            } else if ("Synthetic".equals(attrName)) {
                context.access |= Opcodes.ACC_SYNTHETIC
                        | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
            } else if (ANNOTATIONS
                    && "RuntimeInvisibleAnnotations".equals(attrName)) {
                ianns = u + 8;
            } else if (ANNOTATIONS
                    && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
                itanns = u + 8;
            } else if (ANNOTATIONS
                    && "RuntimeVisibleParameterAnnotations".equals(attrName)) {
                mpanns = u + 8;
            } else if (ANNOTATIONS
                    && "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
                impanns = u + 8;
            } else if ("MethodParameters".equals(attrName)) {
                methodParameters = 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、访问方法描述信息
        MethodVisitor mv = classVisitor.visitMethod(context.access,
                context.name, context.desc, signature, exceptions);
        if (mv == null) {
            return u;
        }

        /*
         * if the returned MethodVisitor is in fact a MethodWriter, it means
         * there is no method adapter between the reader and the writer. If, in
         * addition, the writers constant pool was copied from this reader
         * (mw.cw.cr == this), and the signature and exceptions of the method
         * have not been changed, then it is possible to skip all visit events
         * and just copy the original code of the method to the writer (the
         * access, name and descriptor can have been changed, this is not
         * important since they are not copied as is from the reader).
         */
        if (WRITER && mv instanceof MethodWriter) {
            MethodWriter mw = (MethodWriter) mv;
            if (mw.cw.cr == this && signature == mw.signature) {
                boolean sameExceptions = false;
                if (exceptions == null) {
                    sameExceptions = mw.exceptionCount == 0;
                } else if (exceptions.length == mw.exceptionCount) {
                    sameExceptions = true;
                    for (int j = exceptions.length - 1; j >= 0; --j) {
                        exception -= 2;
                        if (mw.exceptions[j] != readUnsignedShort(exception)) {
                            sameExceptions = false;
                            break;
                        }
                    }
                }
                if (sameExceptions) {
                    /*
                     * we do not copy directly the code into MethodWriter to
                     * save a byte array copy operation. The real copy will be
                     * done in ClassWriter.toByteArray().
                     */
                    mw.classReaderOffset = firstAttribute;
                    mw.classReaderLength = u - firstAttribute;
                    return u;
                }
            }
        }

        // 4、访问方法参数信息
        if (methodParameters != 0) {
            for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
                mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
            }
        }

        // 5、访问方法的注解信息
        if (ANNOTATIONS && dann != 0) {
            AnnotationVisitor dv = mv.visitAnnotationDefault();
            readAnnotationValue(dann, c, null, dv);
            if (dv != null) {
                dv.visitEnd();
            }
        }
        if (ANNOTATIONS && anns != 0) {
            for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
                v = readAnnotationValues(v + 2, c, true,
                        mv.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,
                        mv.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,
                        mv.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,
                        mv.visitTypeAnnotation(context.typeRef,
                                context.typePath, readUTF8(v, c), false));
            }
        }
        if (ANNOTATIONS && mpanns != 0) {
            readParameterAnnotations(mv, context, mpanns, true);
        }
        if (ANNOTATIONS && impanns != 0) {
            readParameterAnnotations(mv, context, impanns, false);
        }

        // 6、访问方法的属性信息
        while (attributes != null) {
            Attribute attr = attributes.next;
            attributes.next = null;
            mv.visitAttribute(attributes);
            attributes = attr;
        }

        // 7、访问方法代码对应的字节码信息
        if (code != 0) {
            mv.visitCode();
            readCode(mv, context, code);
        }

        // 8、visits the end of the method
        mv.visitEnd();

        return u;
    }

同类和字段的读取、访问套路一样,首先,在注释1和注释2处,会 先开始进行方法相关的字节码解析的工作:读取了方法的描述和属性信息。然后,在注释3 ~ 注释7处 按顺序访问了方法的描述、参数、注解、属性、方法代码对应的字节码信息。需要注意的是,在 readCode 方法中,也是先读取了方法内部代码的字节码信息,例如头部、属性等等,然后,便会访问对应的指令集。最后,在注释8处 调用了 MethodVisitor 实例的 visitEnd 方法结束了方法信息的访问。

ClassReader accpet ClassVisitor时开始解析字节码,并将保存在内存中的结果源源不断地通过调用各种 visitxxx 方法传入到 ClassVisitor 之中。
只有 visit 这个方法一定会被调用一次,因为它 获取了类头部的描述信息

在这里插入图片描述
**ASM Core API 类似于解析 XML 文件中的 SAX 方式,直接用流式的方法来处理字节码文件,而不需要把这个类的整个结构读进内存之中。**其好处是能够尽可能地节约内存,难度在于编程时需要有一定的 JVM 字节码基础。由于它的性能较好,所以通常情况下我们都会直接使用 Core API。下面,我们再来回顾下 事件模型中 Core API 的关键组件,如下所示:

1)、ClassReader:用于读取已经编译好的 .class 文件。
2)、ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。
3)、各种 Visitor 类:如上所述,Core API 根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的 Visitor,比如用于访问方法的 MethodVisitor、用于访问类变量的 FieldVisitor、用于访问注解的 AnnotationVisitor 等等。为了实现 AOP,其重点是要灵活运用 MethodVisitor。

在使用 ASM 进行插桩的时候,我们尤其需要注意以下 两点:

1)、当我们使用 ASM 处理字节码时,需要 逐步小量的修改、验证,切记不要编写大量的字节码并希望它们能够立即通过验证并且可以马上执行。比较稳妥的做法是,每编写一行字节码时就考虑一下操作数栈与局部变量表之间的变化情况,确定无误之后再写下一行。此外,除了 JVM 的验证器之外,ASM 还维护了一个单独的字节码验证器,它也会检查你的字节码实现是否符合 JVM 规范。
2)、注意本地变量表和操作数栈的数据交换以及 try catch blcok 的处理,关于异常处理可以使用 ASM 提供的 CheckClassAdapter,可以在修改完成后验证一下字节码是否正常。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的代码家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值