ASM的基本使用

3 篇文章 0 订阅
1 篇文章 0 订阅

ASM是什么:ASM是一个通用的Java字节码操作和分析框架。 它可以用于修改现有类或直接以二进制形式动态生成类。 ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。 ASM提供与其他Java字节码框架类似的功能,但专注于性能。 因为它的设计和实现尽可能小而且快,所以它非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。

 

1、如何用ASM读出整个类:

利用ASM的ClassReader将class文件或Stream流读进ClassReader中。

ClassReader reader = new ClassReader(ClassPrinter.class.getClassLoader().getResourceAsStream("com/jzh/designpattern/proxy/Movable.class"));

ClassReader reader = new ClassReader("com.jzh.designpattern.proxy.Movable");

2、如何对ClassReader读出的类进行操作:

利用ClassVisitor,当传入某个类时,这个类会通过访问者模式遍历ClassVisitor的每一个节点,ClassVisitork节点大致有以下几个:

方法名称描述节点
visit访问类信息

ClassVisitor

visitField访问类成员变量或类变量信息

FieldVisitor

visitMethod访问类中的方法MethodVisitor

 visitAnnotation

访问类的注释信息AnnotationVisitor

 

可以通过继承ClassVisitor重写ClassVisitor的方法从而对类进行操作。

public class ClassPrinter extends ClassVisitor {
    public ClassPrinter() {
        super(ASM4);
    }

    public ClassPrinter(ClassWriter classWriter) {
        super(ASM4, classWriter);
    }

    /**
     * 访问类
     * @param version jdk版本
     * @param access 访问修饰符
     * @param name 类名
     * @param signature 泛型信息
     * @param superName 父类类名
     * @param interfaces 实现的接口
     */
    @Override
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
        System.out.print(name + " ");
        if (signature != null) {
            System.out.print("<" + signature + "> ");
        }
        if (signature != null) {
            System.out.print("extends " + superName + " ");
        }
        if (interfaces != null && interfaces.length > 0) {
            System.out.print("implements " + interfaces[0]);
            for (int i = 1; i < interfaces.length; i++) {
                System.out.print(", " + interfaces[i]);
            }
        }
        System.out.println(" {");
        super.visit(version, access, name, signature, superName, interfaces);
    }

    /**
     * 访问类成员变量
     * @param access 访问修饰符
     * @param name 方法名
     * @param desc 返回类型
     * @param signature 泛型类型
     * @param value 初始值(final时才会打印)
     * @return
     */
    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        System.out.println("        " + desc + " " + name + " ");
        return super.visitField(access, name, desc, signature, value);
    }

    /**
     * 访问类方法
     * @param access 访问修饰符
     * @param name 方法名称
     * @param desc 方法返回类型
     * @param signature 泛型
     * @param exceptions 抛出的异常
     * @return
     */
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println("        " + desc + " " + name + "();");
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitEnd() {
        System.out.println("}");
        super.visitEnd();
    }
}

如何向ClassVisitor传入类:

ClassPrinter classPrinter = new ClassPrinter();
ClassReader reader = new ClassReader("java.lang.Runnable");
reader.accept(classPrinter, 0);

运行结果:

java/lang/Runnable  {
        ()V run();
}

Process finished with exit code 0

3、MethodVisitor

MethodVisitor节点内部也是一个visitor,可以继承MethodVisitor对方法进行操作,例如:

在调用名称为move的方法前,静态调用LogProxy的before方法,在调用名称为move2的方法前,静态调用LogProxy的before2方法

ClassVisitor classVisitor = new ClassVisitor(ASM4, classWriter) {
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        if (name.equals("move")) {
            return new MethodVisitor(ASM4, super.visitMethod(access, name, descriptor, signature, exceptions)) {
                @Override
                public void visitCode() {
                    super.visitMethodInsn(INVOKESTATIC, "com/jzh/designpattern/proxy/asm/LogProxy", "before", "()V", false);
                            super.visitCode();
                }

                @Override
                public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
                    super.visitFieldInsn(opcode, owner, name, descriptor);
                }

                @Override
                public void visitEnd() {
                    super.visitEnd();
                }
            };
        } else if (name.equals("move2")) {
            return new MethodVisitor(ASM4, super.visitMethod(access, name, descriptor, signature, exceptions)) {
                @Override
                public void visitCode() {
                    super.visitMethodInsn(INVOKESTATIC, "com/jzh/designpattern/proxy/asm/LogProxy", "before2", "()V", false);
                    super.visitCode();
                }

            };
        }
        return new MethodVisitor(ASM4, super.visitMethod(access, name, descriptor, signature, exceptions)) {};
    }
};

附:官方文档解释:

The visitCode and visitMaxs methods can therefore be used to detect
the start and end of the method’s bytecode in a sequence of events.
Like for classes, the visitEnd method must be called last, and is used
to detect the end of a method in a sequence of events

执行过程:

较重要方法解释:

MethodVisitor常用方法名描述
visitCodeASM开始扫描方法
visitMaxs用以确定类方法在执行时候的堆栈大小
visitEnd表示方法输出完毕

 

4、如何将类写入内存:

①用ClassWriter写一个类(各参数常量储存在org.objectweb.asm.Opcodes中)

ClassWriter cw = new ClassWriter(0);
// 生成类或者接口
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/ASMWriteClassTest",
                null, "java/lang/Object", null);
// 生成成员变量
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "testField", "I", null, 10).visitEnd();
// 生成方法
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "testMethod", "(Ljava/lang/Object;)I", null, null).visitEnd();

cw.visitEnd();

②定义一个classLoader继承自ClassLoader,并将写好的类加载到内存中

public class MyClassLoader extends ClassLoader {
    public Class<?> defineClass(String name, byte[] bytes) {
        return defineClass(name, bytes, 0, bytes.length);
    }
}
byte[] b = cw.toByteArray();
MyClassLoader myClassLoader = new MyClassLoader();
Class c = myClassLoader.defineClass("pkg.ASMWriteClassTest", b);

5、如何用ASM实现动态代理:

①利用ClassReader将目标类读入到ClassReader中

ClassReader reader = new ClassReader(ASMProxyTest.class.getClassLoader().getResourceAsStream("com/jzh/designpattern/proxy/Car.class"));

②将ClassWriter放入到ClassVisitor中

ClassWriter classWriter = new ClassWriter(0);
ClassVisitor classVisitor = new ClassPrinter(classWriter);

③用ClassVisitor对ClassReader进行访问,使得将修改后的目标类加载到了ClassWriter中

reader.accept(classVisitor, 0);

④用ClassLoader加载目标类,并将ClassLoader中的目标类重新定义得到新的Class对象

byte[] bytes = classWriter.toByteArray();
MyClassLoader classLoader = new MyClassLoader();
classLoader.loadClass("com.jzh.designpattern.proxy.Car");
Class c2 = classLoader.defineClass("com.jzh.designpattern.proxy.Car", bytes);

⑤利用反射获取类的实例

Movable moveable = null;
try {
    moveable = (Movable) c2.getDeclaredConstructor().newInstance(null);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
}
moveable.move();
moveable.move2();

打印结果:

proxy start...
car moving 1...
proxy start2...
car moving 2...

主要参考资料:https://blog.csdn.net/qq_33589510/article/details/105273233https://cloud.tencent.com/developer/article/1633443

ASM官方文档:https://asm.ow2.io/asm4-guide.pdf

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值