ASM原理分析

    ASM是一款十分优秀的字节码处理框架,在很多地方都有用到,像fastJson序列化、反序列化时,针对用户定义的class类型去生成新的类。像在Spring的aop的动态代理中,用到了ASM做类的代理。有了asm,可以帮助我们解决很多问题,确实是值得深入了解的一个优秀武器。

ASM的设计模式

  在了解ASM之前,先来说一下ASM的整体设计模式-访问者模式(visitor)。

  这种模式用的比较少,但是思路上并不复杂。简单来说,就是定义若干元素类和若干访问者类,在访问者类定义了改变了元素类的执行算法。这也就意味着元素的内部方法会随着访问者改变而改变。上个例子:

//访问者的接口,这里定义了对两种元素的访问方法
//也可以用抽象类,在子类中重写方法
public interface Visitor {
    void visit(MyElement1 element);
    void visit(MyElement2 element);
}
//第一个visitor类,MyVisitor1。定义了对两种元素的执行方法。
public class MyVisitor1 implements Visitor {

    @Override
    public void visit(MyElement1 element) {
        log.info("this is MyVisitor1, visit MyElement1");
    }

    @Override
    public void visit(MyElement2 element) {
        log.info("this is MyVisitor1, visit MyElement2");
    }
}

//第二个visitor类,MyVisitor2。定义了对两种元素的执行方法。
public class MyVisitor2 implements Visitor {

    @Override
    public void visit(MyElement1 element) {
        log.info("this is MyVisitor2, visit MyElement1");
    }

    @Override
    public void visit(MyElement2 element) {
        log.info("this is MyVisitor2, visit MyElement2");
    }
}

  然后定义元素类: 

//元素接口,定义了一个接收方法。
public interface Element {
    void accept(Visitor visitor);
}
//第一个元素类MyElement1,定义一个accept方法
public class MyElement1 implements Element {
    public void accept(Visitor visitor) {
       log.info(visitor.getClass().getCanonicalName()+"访问MyElement1");
        visitor.visit(this);
    }
}

//第二个元素类MyElement2,定义一个accept方法
public class MyElement2 implements Element {
    public void accept(Visitor visitor) {
       log.info(visitor.getClass().getCanonicalName()+"访问MyElement2");
        visitor.visit(this);
    }
}

  调用可以有很多方式,简单的比如直接生成对象,然后调用accept方法:

   Visitor visitor = new MyVisitor2();
   Element element = new MyElement1();
   element.accpet(visitor);

  总体来说,访问者模式的优缺点都很明显:

(1)解耦元素和执行者,自定义访问者实现对元素的动态执行。

(2)但是出现新的元素时,所有的访问者类都需要增加相应的访问方法。

 所以访问者模式并不适合元素类频繁变动的场景,对于大多数的业务代码场景来说,不变的元素类是很少的,在具体使用过程中要评估这种场景。但是ASM的元素类是class字节码类,这个是不变的,所以天然适合这种设计模式。

ASM的逻辑

  铺垫完之后,以ClassReader为例,来看看ASM的执行逻辑:

  ClassReader和ClassWriter一起可以实现对class文件的字节码做自定义的操作之后,再动态生成一个新的class文件。

  先看ClassReader类中的accept方法:(参考下面的Class文件格式)

    public void accept(final ClassVisitor classVisitor,
            final Attribute[] attrs, final int flags) {
        //u代表当前class文件的读取位置,head代表access_flags的位置。此时把header赋值给u,代表从头开始读class文件
        int u = header; // current offset in the class file
        //定义一个char数组用来读取字符串
        char[] c = new char[maxStringLength]; // buffer used to read strings
        
        //定义一个上下文
        Context context = new Context();
        context.attrs = attrs;
        context.flags = flags;
        context.buffer = c;

        // reads the class declaration
        int access = readUnsignedShort(u);
        //access_flags是两个字节,name是读取的类名称
        String name = readClass(u + 2, c);
        //superClass是读取的父类名称
        String superClass = readClass(u + 4, c);
        //读取interfaces集合,指针u一致往后移动
        String[] interfaces = new String[readUnsignedShort(u + 6)];
        u += 8;
        for (int i = 0; i < interfaces.length; ++i) {
            interfaces[i] = readClass(u, c);
            u += 2;
        }

        // reads the class attributes
        String signature = null;
        String sourceFile = null;
        String sourceDebug = null;
        String enclosingOwner = null;
        String enclosingName = null;
        String enclosingDesc = null;
        String moduleMainClass = null;
        int anns = 0;
        int ianns = 0;
        int tanns = 0;
        int itanns = 0;
        int innerClasses = 0;
        int module = 0;
        int packages = 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 {
                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);
        }

        //访问者模式,开始调用访问者的不同访问方法,将属性全部转发出去   
        // visits the class declaration
        //访问:类的申明
        //items中存放的常量池每项的开始索引加1,加1是跳过常量池项标记,截取方便一点。
        classVisitor.visit(readInt(items[1] - 7), access, name, signature,
                superClass, interfaces);

        // visits the source and debug info
        //读取资源信息
        if ((flags & SKIP_DEBUG) == 0
                && (sourceFile != null || sourceDebug != null)) {
            classVisitor.visitSource(sourceFile, sourceDebug);
        }

        // visits the module info and associated attributes
        if (module != 0) {
            readModule(classVisitor, context, module,
                    moduleMainClass, packages);
        }

        // visits the outer class
        if (enclosingOwner != null) {
            classVisitor.visitOuterClass(enclosingOwner, enclosingName,
                    enclosingDesc);
        }

        // visits the class annotations and type annotations
        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));
            }
        }

        // visits the attributes
        while (attributes != null) {
            Attribute attr = attributes.next;
            attributes.next = null;
            classVisitor.visitAttribute(attributes);
            attributes = attr;
        }

        // visits the inner classes
        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;
            }
        }

        // visits the fields and methods
        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);
        }

        // visits the end of the class
        classVisitor.visitEnd();
    }

    总的来说,在accept方法中,就是从access_flag后开始读取,按照class属性的顺序一个个解析出来。 在后面再转发到visitor的方法里面。

   提下Class文件结构:

   回忆一下,Class文件是一组8位字节为基础单位的二进制流,各个数据项严格按照顺序紧凑的排列,中间是没有添加任何分隔符。

public class ClassFile {
    //魔数,确定这个文件是否为一个能被虚拟机接收,为固定值。  
    public final int magic;
    //次版本号
    public final int minor_version;
    //主版本号
    public final int major_version;
    //常量池
    public final ConstantPool constant_pool;
    //访问标志    
    public final AccessFlags access_flags;
    //类索引    
    public final int this_class;
    //父类索引
    public final int super_class;
    //接口索引集合
    public final int[] interfaces;
    //字段表集合
    public final Field[] fields;
    //方法表集合
    public final Method[] methods;
    //属性表集合
    public final Attributes attributes;
 
     。。。
}

再看下观察者模式的”访问者“,ClassWriter:

//这里的访问者没有定义接口,而是继承父类的方式,这样不用实现所有方法。
public class ClassWriter extends ClassVisitor {

   。。。

   //针对Class文件各个属性,不同的visit方法。     
   //默认的visit方法,把元素类中的属性做赋值操作,全部放倒自己的字段里面。
   @Override
    public final void visit(final int version, final int access,
            final String name, final String signature, final String superName,
            final String[] interfaces) {
        this.version = version;
        this.access = access;
        this.name = newClass(name);
        thisName = name;
        if (ClassReader.SIGNATURES && signature != null) {
            this.signature = newUTF8(signature);
        }
        this.superName = superName == null ? 0 : newClass(superName);
        if (interfaces != null && interfaces.length > 0) {
            interfaceCount = interfaces.length;
            this.interfaces = new int[interfaceCount];
            for (int i = 0; i < interfaceCount; ++i) {
                this.interfaces[i] = newClass(interfaces[i]);
            }
        }
    }

    @Override
    public final void visitSource(final String file, final String debug) {
        if (file != null) {
            sourceFile = newUTF8(file);
        }
        if (debug != null) {
            sourceDebug = new ByteVector().encodeUTF8(debug, 0,
                    Integer.MAX_VALUE);
        }
    }

   。。。

}

   可以看出来,ClassWriter这个”访问者“,实际上做的事就是持有元素类ClassReader中分析出来的Class文件中的各个属性。那么我们就可以仿照着这种方式对class文件做我们想做的事了,比如说加个字段,改变方法等等,感觉还是很激动的有没有,可以直接改字节码了。

总结:

(1)ASM是一款十分优秀的字节码处理框架,可以对Class文件做我们想要的改动。

(2)ASM天然的适合访问者模式,因为Class文件的格式是固定的,各个数据项严格按照顺序紧凑的排列。所以不用担心扩展的问题。

(3)ClassReader做为“元素类”,ClassWriter做为”访问者“,在ClassReader的accept方法中,就是从access_flag后开始读取,按照class属性的顺序一个个解析出来。 在后面再转发到ClassWriter的不同visit方法里面。

(4)灵活的运用ASM,可以解决很多问题。另外,基于此库可以做很多有意思的事。

   

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页