ASM学习笔记1 - 初识ClassVistor ——以ClassReader的应用为例

ASM学习笔记1 - 初识ClassVistor ——以ClassReader的应用为例

1 ASM简介

什么是ASM?

Java操纵类字节码的工具。是一个jar包。

如何使用?

ASM提供两类API,能够分别将类表示为事件对象。我们先主要了解核心API,即能够将类以事件方式操纵的方式,即用基于事件的模型

在采用基于事件的模型时,类是用一系列事件来表示的,每个事件表示类的一个元素,比 如 它的一个标头、一个字段、一个方法声明、一条指令,等等。基于事件的 API 定义了一组 可能 事件,以及这些事件必须遵循的发生顺序,还提供了一个类分析器,为每个被分析元素生 成一个 事件,还提供一个类写入器,由这些事件的序列生成经过编译的类。

下文会介绍该API来使用。

2 类操作

这一部分将会描述asm对java类文件的操作。在这之前,我们需要对java类本身的结构有一定了解。

class知识点

  1. 在class内,数据类型以类型描述符表示。一些常用的类型标识符,如:int,表示为I;int数组,表示为[I。而对于非原生数据类型,如用户自定类型,String,Object类型,则使用L+内部名+;表示。内部名是类的完全限定名,以层级关系以/分割。例如,String 的内部名为 java/lang/String。因此,String的类型描述符为Ljava/lang/String;,String数组的描述符为[Ljava/lang/String;。
  2. 同样,方法也有方法描述符。具体方式如下:

返 回 值 ( 变 量 1 , 变 量 2 , . . . ) → ( 变 量 1 描 述 符 + 变 量 2 描 述 符 , . . . ) 返 回 值 描 述 符 返回值(变量1,变量2,...) \rightarrow (变量1描述符+变量2描述符,...)返回值描述符 (1,2,...)(1+2,...)

例如,一个接受两个int为输入,String为输出的方法,方法描述符为(II)Ljava/lang/string;。注意,若输入为空,则括号内不填。若输出为空,则用V表示。

接下来开始正经介绍相关API:

3 ClassVistor

ClassVistor用于访问class,本身是抽象类。**定义在读取Class字节码时会触发的事件。**只要将所需执行的操作写入对应方法下,调用ClassVistor的其他类就能在对应的条件下触发他们。

以下为一些API:

public ClassVisitor(int api, ClassVisitor cv);
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
public void visitSource(String source, String debug);
public void visitOuterClass(String owner, String name, String desc); 
AnnotationVisitor visitAnnotation(String desc, boolean visible); 
public void visitAttribute(Attribute attr);
public void visitInnerClass(String name, String outerName, String innerName, int access);
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value);
public MethodVisitor visitMethod(int access, String name,String desc,String signature, String[] exceptions);
void visitEnd();

注意,ClassVistor对方法的访问是有顺序的:

这意味着必须首先调用 visit,然后是对 visitSource 的最多一个调用,接下来是对 visitOuterClass 的最多一个调用 , 然后是可按任意顺序对 visitAnnotation 和 visitAttribute 的任意多个访问 , 接下来是可按任意顺序对 visitInnerClass 、 visitField 和 visitMethod 的任意多个调用,最后以一个 visitEnd 调用结束。

一般我们使用时,只关心对类内的方法和域的访问。因此,我们只需了解在调用visitMethod与visitField前后需要使用visit和visitEnd即可。

4 ClassReader

该类类如其名,用来“阅读”class文件,即分析一个已经存在的类每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理。

构造器:

在这里插入图片描述

使用InputStream或byte[]获取类的字节,或直接通过String的类名称来获取类。

例:

ClassReader cr = new ClassReader("java.util.ArrayList");
System.out.println(cr.getSuperName());

我们使用了String作为输入的构造器,ClassReader接下来会处理ArrayList这个类。在第二行中,其便打印出了它的超类。以下为输出:

java/util/AbstractList

ClassReader也可分析用户类。例如IDEA中,我们需要访问clazz包下的Hello.class:

ClassReader cr = new ClassReader("clazz.Hello");

方法:

ClassReader提供了一系列get方法获取类信息:

在这里插入图片描述

不过,它最重要的方法还是accept方法:

在这里插入图片描述

accept可以接受一个ClassVisitor,接收后便开始读取数据。当满足一定条件时,就会触发ClassVisitor下的方法。且看以下代码:

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;

import java.io.IOException;

import static jdk.internal.org.objectweb.asm.Opcodes.*;

public class Test {

    public static void main(String[] var0) throws IOException {
        ClassReader cr = new ClassReader("java.util.ArrayList");
        cr.accept(new MyClassVisitor(ASM4),0);
    }

    static class MyClassVisitor extends ClassVisitor {

        public MyClassVisitor(int i) {
            super(i);
        }

        @Override
        public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
            System.out.println("访问方法 " + s);
            return super.visitMethod(i, s, s1, s2, strings);
        }
    }
}

我们继承了ClassVisitor类并重写了visitMethod方法。还记得我们之前所说的吗?ClassVistor定义了在读取Class字节码时会触发的事件。我们通过accept方法,建立了ClassVisitor与ClassReader之间的连接。因此,当ClassReader访问对象的方法时,它将触发ClassVisitor内的visitMethod方法,这时由于我们在visitMethod下添加了一条println语句,这样我们就能获取所有的方法名了。

上述代码执行结果如下:

访问方法 <init>
访问方法 <init>
访问方法 <init>
访问方法 trimToSize
访问方法 ensureCapacity
访问方法 calculateCapacity
访问方法 ensureCapacityInternal
访问方法 ensureExplicitCapacity
访问方法 grow
访问方法 hugeCapacity
访问方法 size
访问方法 isEmpty
访问方法 contains
访问方法 indexOf
访问方法 lastIndexOf
访问方法 clone
访问方法 toArray
访问方法 toArray
访问方法 elementData
访问方法 get
访问方法 set
访问方法 add
访问方法 add
访问方法 remove
访问方法 remove
访问方法 fastRemove
访问方法 clear
访问方法 addAll
访问方法 addAll
访问方法 removeRange
访问方法 rangeCheck
访问方法 rangeCheckForAdd
访问方法 outOfBoundsMsg
访问方法 removeAll
访问方法 retainAll
访问方法 batchRemove
访问方法 writeObject
访问方法 readObject
访问方法 listIterator
访问方法 listIterator
访问方法 iterator
访问方法 subList
访问方法 subListRangeCheck
访问方法 forEach
访问方法 spliterator
访问方法 removeIf
访问方法 replaceAll
访问方法 sort
访问方法 access$000
访问方法 <clinit>

这样我们就获取了ArrayList下的所有方法。

更进一步:MethodVisitor和ClassVisitor的本质是一样的。既然我们通过了visitMethod能够返回MethodVisitor对象,我们能不能更进一步,找出ArrayList下的方法内又使用了那些方法呢?提示:MethodVisitor下的visitMethodInsn方法也会在访问方法时触发。建议读者在上述的代码的基础上自己尝试。以下是参考答案:

import com.sun.xml.internal.ws.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;

import java.io.IOException;
import java.util.ArrayList;

import static jdk.internal.org.objectweb.asm.Opcodes.*;

public class Test {

    public static void main(String[] var0) throws IOException {
        ClassReader cr = new ClassReader("java.util.ArrayList");
        cr.accept(new MyClassVisitor(ASM4),0);

    }

    static class MyClassVisitor extends ClassVisitor {

        public MyClassVisitor(int i) {
            super(i);
        }

        @Override
        public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
            System.out.println("访问方法 " + s);
            MethodVisitor mv = super.visitMethod(i, s, s1, s2, strings);
            return new MyMethodVisitor(ASM4,mv);
        }
    }

    static class MyMethodVisitor extends MethodVisitor {

        public MyMethodVisitor(int i, MethodVisitor methodVisitor) {
            super(i, methodVisitor);
        }

        @Override
        public void visitMethodInsn(int i, String s, String s1, String s2, boolean b) {
            System.out.println("\t-> " + s1);
            super.visitMethodInsn(i, s, s1, s2, b);
        }
    }
}

输出如下:

访问方法 <init>
	-> <init>
	-> <init>
	-> append
	-> append
	-> toString
	-> <init>
访问方法 <init>
	-> <init>
访问方法 <init>
	-> <init>
	-> toArray
	-> getClass
	-> copyOf
访问方法 trimToSize
	-> copyOf
访问方法 ensureCapacity
	-> ensureExplicitCapacity
访问方法 calculateCapacity
	-> max
访问方法 ensureCapacityInternal
	-> calculateCapacity
	-> ensureExplicitCapacity
访问方法 ensureExplicitCapacity
	-> grow
访问方法 grow
	-> hugeCapacity
	-> copyOf
访问方法 hugeCapacity
	-> <init>
访问方法 size
访问方法 isEmpty
访问方法 contains
	-> indexOf
访问方法 indexOf
	-> equals
访问方法 lastIndexOf
	-> equals
访问方法 clone
	-> clone
	-> copyOf
	-> <init>
访问方法 toArray
	-> copyOf
访问方法 toArray
	-> getClass
	-> copyOf
	-> arraycopy
访问方法 elementData
访问方法 get
	-> rangeCheck
	-> elementData
访问方法 set
	-> rangeCheck
	-> elementData
访问方法 add
	-> ensureCapacityInternal
访问方法 add
	-> rangeCheckForAdd
	-> ensureCapacityInternal
	-> arraycopy
访问方法 remove
	-> rangeCheck
	-> elementData
	-> arraycopy
访问方法 remove
	-> fastRemove
	-> equals
	-> fastRemove
访问方法 fastRemove
	-> arraycopy
访问方法 clear
访问方法 addAll
	-> toArray
	-> ensureCapacityInternal
	-> arraycopy
访问方法 addAll
	-> rangeCheckForAdd
	-> toArray
	-> ensureCapacityInternal
	-> arraycopy
	-> arraycopy
访问方法 removeRange
	-> arraycopy
访问方法 rangeCheck
	-> outOfBoundsMsg
	-> <init>
访问方法 rangeCheckForAdd
	-> outOfBoundsMsg
	-> <init>
访问方法 outOfBoundsMsg
	-> <init>
	-> append
	-> append
	-> append
	-> append
	-> toString
访问方法 removeAll
	-> requireNonNull
	-> batchRemove
访问方法 retainAll
	-> requireNonNull
	-> batchRemove
访问方法 batchRemove
	-> contains
	-> arraycopy
	-> arraycopy
访问方法 writeObject
	-> defaultWriteObject
	-> writeInt
	-> writeObject
	-> <init>
访问方法 readObject
	-> defaultReadObject
	-> readInt
	-> calculateCapacity
	-> getJavaOISAccess
	-> checkArray
	-> ensureCapacityInternal
	-> readObject
访问方法 listIterator
	-> <init>
	-> append
	-> append
	-> toString
	-> <init>
	-> <init>
访问方法 listIterator
	-> <init>
访问方法 iterator
	-> <init>
访问方法 subList
	-> subListRangeCheck
	-> <init>
访问方法 subListRangeCheck
	-> <init>
	-> append
	-> append
	-> toString
	-> <init>
	-> <init>
	-> append
	-> append
	-> toString
	-> <init>
	-> <init>
	-> append
	-> append
	-> append
	-> append
	-> append
	-> toString
	-> <init>
访问方法 forEach
	-> requireNonNull
	-> accept
	-> <init>
访问方法 spliterator
	-> <init>
访问方法 removeIf
	-> requireNonNull
	-> <init>
	-> test
	-> set
	-> <init>
	-> nextClearBit
	-> <init>
访问方法 replaceAll
	-> requireNonNull
	-> apply
	-> <init>
访问方法 sort
	-> sort
	-> <init>
访问方法 access$000
访问方法 <clinit>
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值