ASM框架研究

ASM是什么

ASM是一个致力于字节码和分析的框架,它用来动态生成类或者增强既有类的功能。ASM可以直接创建class文件,也可以在类被加载到jvm之前动态的改变类的行为。class文件是按照严格格式存储的,这些class文件拥有足够的元数据来解析类中所有元素:类名称、方法、属性以及Java字节码指令,ASM能够直接从class文件中读取信息获得类信息,同时也能根据需求直接通过字节码修改class文件。虽然,ASM提供了其他字节码工具相同的功能,但是它更关注执行效率,它被设计的更小更快。其实,jdk内部已经使用了ASM,同学们可以看一下jdk.interbal.org.objectweb.asm包,在实现lambda表达式调用, Nashorn编译器时都有用到。

ASM基础

Opcodes接口定义了一些常量,如版本号,访问标识符,字节码等信息

ClassReader用于读取class文件,主要用于class文件分析,可接受一个ClassVisitor;ClassReader会将解析过程中产生的类的部分信息,比如访问标识符,字段,方法存在ClassVisitor中,后者在接收到对应信息后,进行各自的处理。

ClassWriter是ClassVisitor的子类,负责calss文件的输出和生成。ClassVisitor在进行字段和方法处理的时候,会委托给FieldVisitor和MethodVisitor进行处理。FieldWriter和MethodWriter是FieldVisitor和MethodVisitor的子类;当ClassWriter依赖这两个类进行字段和方法的处理。

ClassWriter的参数:
0 :你需要手动计算,最大操作数栈,局部变量表,桢变化
COMPUTE_MAXS:自动计算局部变量表和操作数栈,但是必须要调用visitMaxs,方法参数会被忽略。桢变化需要手动计算
COMPUTE_FRAMES:全自动计算,但是必须要调用visitMaxs,方法参数会被忽略。
但是有时间成本,COMPUTE_MAXS比0慢10%,COMPUTE_FRAMES慢一倍。

下面我们介绍一下ClassVisitor,还是常用方式看源码

/**
 * 访问一个类,这个类的方法必须按照下面的顺序调用:
 * A visitor to visit a Java class. The methods of this class must be called in the following order:
 * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code
 * visitPermittedSubtype} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code
 * visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code
 * visitInnerClass} | {@code visitField} | {@code visitMethod} )* {@code visitEnd}.
 *
 * @author Eric Bruneton
 */
public abstract class ClassVisitor {

通过上面的注释我们知道访问是有顺序的。
ClassVisitor,FieldVisitor,MethodVisitor(使用这些类前阅读以下源码的注释,visitXX方法的调用,都是有顺序的)都可以使用委托的方式,将实际工作交给内部委托类进行的。

下面我们介绍descriptor:主要是对方法参数和返回值进行描述

  1. 字节码指令中的数据类型:
Java TypeType descriptor
booleanZ
charC
byteB
shortS
intI
floatF
longJ
doubleD
ObjectLjava/lang/Object
int[][I
Object[][][[Ljava/lang/Object
  1. 字段和方法中描述符或签名的数据类型:
Method declaration in source fileMethod descriptor
void m(int i, float f)(IF)V
int m(Object o)(Ljava/lang/Object;)I
int[] m(int i, String s)(ILjava/lang/String;)[I
Object m(int[] i)([I)Ljava/lang/Object;

下面我们介绍signature:泛型中才会将该属性编译进字节码文件,除了方法参数和返回值,还包含了泛型信息
举个例子:

public class Test<E> {
    private E e;
    
    public Test(E e) {
        this.e = e;
    }

   /**
     *descriptor:(Ljava/lang/Object;)V
     *signature: <E:Ljava/lang/Object;>(TE;)V
     */
    public <E> void sysE(E e) {
        ...
    }

    /**
     *descriptor:(Ljava/lang/Object;)V
     *signature: (TE;)V
     */
    public void sys(E e) {
        ...
    }
}

T表示参数是泛型类型,E表示其签名,V表示返回值。

了解了上面的基础知识后,我们开始进行实地操作。

ASM使用举例:

1.ClassWriter生成字节码

public class ASMTest {

    public static void main(String[] args) throws IOException {
        // 生成一个类只需要ClassWriter组件即可
        ClassWriter cw = new ClassWriter(0);
        //通过visit方法确定类的头部信息
        cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE,
                "com/pch/asm/TestASM", null, "java/lang/Object", null);
        //定义类的属性
        cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
                "A", "I", null, 1).visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
                "B", "Ljava/lang/String;", null, "test").visitEnd();
        cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
                "C", "I", null, 3).visitEnd();
        //定义类的方法
        cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "test",
                "(Ljava/lang/Object;)I", null, null).visitEnd();
        cw.visitEnd(); //使cw类已经完成
        //将cw转换成字节数组写到文件里面去
        byte[] data = cw.toByteArray();
        File file = new File("D://TestASM.class");
        FileOutputStream out = new FileOutputStream(file);
        out.write(data);
        out.close();
    }
}

通过IDEA打开生成的class文件,内容如下:

package com.pch.asm;

public interface TestASM {
    int A = 1;
    String B = "test";
    int C = 3;

    int test(Object var1);
}

2.修改一个存在类(修改一个类,直接用类加载器加载的话,只会在这个类加载器中生效)

移除类成员

如果想移除某个方法,只需要返回Null(也就是不返回method)

import org.objectweb.asm.*;

import java.io.FileOutputStream;
import java.io.IOException;

public class RemoveMethod extends ClassVisitor {
    public RemoveMethod(ClassWriter cw) {
        super(Opcodes.ASM8,cw);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, "com/pch/asm/Remove", signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        if (name.startsWith("print")){
            return null;
        }
        return cv.visitMethod(access, name, descriptor, signature, exceptions);
    }

    public static void main(String[] args) throws IOException {
        ClassWriter classWriter=new ClassWriter(0);
        RemoveMethod removeMethod=new RemoveMethod(classWriter);
        ClassReader classReader=new ClassReader("com.pch.asm.RemoveTest");
        classReader.accept(removeMethod,0);

        FileOutputStream fileOutputStream=new FileOutputStream("D://Remove.class");
        fileOutputStream.write(classWriter.toByteArray());
    }
}

操作类

package com.pch.asm;

public class RemoveTest {
    public static String name;

    private int age;

    public int getAge() {
        return age;
    }

    public static String getName() {
        return name;
    }

    void print() {
        System.out.println(age);
    }
}

移除后的class文件内容如下

package com.pch.asm;

public class Remove {
    public static String name;
    private int age;

    public Remove() {
    }

    public int getAge() {
        return this.age;
    }

    public static String getName() {
        return name;
    }
}
添加类成员

比如添加一个字段。为了确保字段名不重复,添加字段的操作,在访问了所有的字段信息之后执行。

import org.objectweb.asm.*;

import java.io.FileOutputStream;
import java.io.IOException;

public class AddField extends ClassVisitor {
    private String filedName = "newAdd";
    private boolean isPresent = false;

    public AddField(ClassWriter cw) {
        super(Opcodes.ASM8,cw);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        if (name.equals(filedName)){
            isPresent = true;
        }
        return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
        if (!isPresent){
            //没有这个字段
            FieldVisitor fv= this.cv.visitField(Opcodes.ACC_PUBLIC, filedName,"I",null,3);
            if (fv!=null){
                fv.visitEnd();
            }
        }
        super.visitEnd();
    }


    public static void main(String[] args) throws IOException {
        ClassWriter classWriter=new ClassWriter(0);
        AddField addField=new AddField(classWriter);
        ClassReader classReader=new ClassReader("com.pch.asm.Remove");
        classReader.accept(addField,0);

        FileOutputStream fileOutputStream=new FileOutputStream("D://AddField.class");
        fileOutputStream.write(classWriter.toByteArray());
    }
}

添加属性后的class文件内容如下


package com.pch.asm;

public class AddTest {
    public static String name;
    private int age;
    public int newAdd;

    public AddTest() {
    }

    public int getAge() {
        return this.age;
    }

    public static String getName() {
        return name;
    }

    void print() {
        System.out.println(this.age);
    }
}
改变修改方法

例如:增加方法的执行耗时

public class DEMO {

    public static void changeClassMethod() {
        try {
            ClassReader reader = new ClassReader("com.pch.asm.TestTime");
            ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
            ClassVisitor change = new ChangeVisitor(writer);
            reader.accept(change, ClassReader.EXPAND_FRAMES);
            try {
                FileOutputStream fos = new FileOutputStream("D:/TestTime.class");
                fos.write(writer.toByteArray());
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法变化visitor
     */
    private static class ChangeVisitor extends ClassVisitor {

        ChangeVisitor(ClassVisitor classVisitor) {
            super(Opcodes.ASM8, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
            if (name.equals("<init>")) {
                return methodVisitor;
            }
            return new ChangeAdapter(Opcodes.ASM8, methodVisitor, access, name, desc);
        }
    }

    /**
     * 使用AdviceAdapter要引入asm-commons
     * 方法体具体变化的操作都在这里操作
     */
    private static class ChangeAdapter extends AdviceAdapter {
        /** 参数的index */
        private int startTimeId = -1;
        /** 方法名 */
        private String methodName;

        ChangeAdapter(int api, MethodVisitor mv, int access, String name, String desc) {
            super(api, mv, access, name, desc);
            methodName = name;
        }

        /**
         * 进入方法体时调用
         */
        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            startTimeId = newLocal(Type.LONG_TYPE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitIntInsn(LSTORE, startTimeId);
        }

        /**
         * 退出方法体调用
         * @param opcode opcode
         */
        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);
            int durationId = newLocal(Type.LONG_TYPE);
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LLOAD, startTimeId);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, durationId);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn("The cost time of " + methodName + "() is ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, durationId);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn(" ms!");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
    }
}

通过上面的介绍我们已经对ASM有了一定了解。

ASM相关我们先介绍到这,有说的不准确的地方欢迎指正,下一篇文章我们介绍另一个字节码框架javassist。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值