大话 AOP利器ASM

来源:互联网 发布:影视特技软件 编辑:程序博客网 时间:2024/04/28 13:44
       首先理清楚ASM 的主要功能,首先需要做的是对class文件的理解。
ClassFile {    u4             magic;    u2             minor_version;    u2             major_version;    u2             constant_pool_count;    cp_info        constant_pool[constant_pool_count-1];    u2             access_flags;    u2             this_class;    u2             super_class;    u2             interfaces_count;    u2             interfaces[interfaces_count];    u2             fields_count;    field_info     fields[fields_count];    u2             methods_count;    method_info    methods[methods_count];    u2             attributes_count;    attribute_info attributes[attributes_count];

    class文件的结构主要不会i变化,但是解析Class文件的算法,却会有不同的实现方式,这时候ASM框架使用了Vistor模式来做解析class,代码如下:

     

public static void main(final String[] args) throws Exception {ClassReader cr = new ClassReader(TestAsm.getClassInputStream("D:\\test\\Tree.class"));ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);cr.accept(cw, ClassReader.SKIP_DEBUG);MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "test", "(I)String",null, null);mw.visitInsn(ILOAD);mw.visitInsn(ISTORE);mw.visitFieldInsn(GETSTATIC, "TestAsm", "analyze", "B");mw.visitEnd();cw.toByteArray();}}

在构造ClassReader的构造方法如下

public ClassReader(final byte[] b, final int off, final int len) {        this.b = b;        // checks the class version        if (readShort(off + 6) > Opcodes.V1_8) {            throw new IllegalArgumentException();        }        // parses the constant pool        items = new int[readUnsignedShort(off + 8)];        int n = items.length;        strings = new String[n];        int max = 0;        int index = off + 10;        //这边解析常量池逻辑        for (int i = 1; i < n; ++i) {            items[i] = index + 1;            int size;            switch (b[index]) {            case ClassWriter.FIELD:            case ClassWriter.METH:            case ClassWriter.IMETH:            case ClassWriter.INT:            case ClassWriter.FLOAT:            case ClassWriter.NAME_TYPE:            case ClassWriter.INDY:                size = 5;                break;            case ClassWriter.LONG:            case ClassWriter.DOUBLE:                size = 9;                ++i;                break;            case ClassWriter.UTF8:            //UTF-8字节读写的逻辑                size = 3 + readUnsignedShort(index + 1);                if (size > max) {                    max = size;                }                break;            case ClassWriter.HANDLE:                size = 4;                break;            // case ClassWriter.CLASS:            // case ClassWriter.STR:            // case ClassWriter.MTYPE            default:                size = 3;                break;            }            index += size;        }        maxStringLength = max;        // the class header information starts just after the constant pool        //这边解析出常量池,之后的属性。        header = index;    }

这边常量池设计的很不错,后面会说到如何在两个类合并,或者方法引入的时候,常量池的变化

在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;        // reads the class declaration        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;        }        // reads the class attributes        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);        }        // visits the class declaration        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 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();    }

这边真心想吐槽下注解,5.0后 jvm spec增加了,很多新的属性,导致ASM代码急剧扩容,小弟刚刚开始看的时候,累的一米。另外写一个完整支持JVM规范的class操作框架,真的不易,里面设计到好多优化的问题

到这个过程里面ClassReader把控制权交给ClassVistor的实现类,来vistor需要的属性了,在vistor中,最核心的方法莫过于readMethod(classVisitor, context, u);里面很多都是围绕他来展开,到这个阶段我们再来回忆下jvm spec中对于mehod属性的描述

method_info {    u2             access_flags;    u2             name_index;    u2             descriptor_index;    u2             attributes_count;    attribute_info attributes[attributes_count];}
Code_attribute {    u2 attribute_name_index;    u4 attribute_length;    u2 max_stack;    u2 max_locals;    u4 code_length;    u1 code[code_length];    u2 exception_table_length;    {   u2 start_pc;        u2 end_pc;        u2 handler_pc;        u2 catch_type;    } exception_table[exception_table_length];    u2 attributes_count;    attribute_info attributes[attributes_count];}

大家可以先思考思考,对指令的解析(待续),在解析完成,ASM把常量池,方法,Feild解析出来的字节码,封装在不同的Vistor实现类中。这边要说下,增加方法,只需要
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "test", "(I)String",null, null);mw.visitInsn(ILOAD);mw.visitInsn(ISTORE);mw.visitFieldInsn(GETSTATIC, "TestAsm", "analyze", "B");mw.visitEnd();
这样ASM就会有个新的MethodVistor对象,在MethodVisitor中new 一个MethodWriter对象,

  @Override    //指令类型 1    public void visitInsn(final int opcode) {        lastCodeOffset = code.length;        // adds the instruction to the bytecode of the method        code.putByte(opcode);        // update currentBlock        // Label currentBlock = this.currentBlock;        if (currentBlock != null) {            if (compute == FRAMES) {                currentBlock.frame.execute(opcode, 0, null, null);            } else {                // updates current and max stack sizes                int size = stackSize + Frame.SIZE[opcode];                if (size > maxStackSize) {                    maxStackSize = size;                }                stackSize = size;            }            // if opcode == ATHROW or xRETURN, ends current block (no successor)            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)                    || opcode == Opcodes.ATHROW) {                noSuccessor();            }        }    }

下面修改常量池的代码来了

@Override    //指令类型5    public void visitFieldInsn(final int opcode, final String owner,            final String name, final String desc) {        lastCodeOffset = code.length;        //这边修改了常量池        Item i = cw.newFieldItem(owner, name, desc);        // Label currentBlock = this.currentBlock;        if (currentBlock != null) {            if (compute == FRAMES) {                currentBlock.frame.execute(opcode, 0, cw, i);            } else {                int size;                // computes the stack size variation                char c = desc.charAt(0);                switch (opcode) {                case Opcodes.GETSTATIC:                    size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);                    break;                case Opcodes.PUTSTATIC:                    size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);                    break;                case Opcodes.GETFIELD:                    size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);                    break;                // case Constants.PUTFIELD:                default:                    size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);                    break;                }                // updates current and max stack sizes                if (size > maxStackSize) {                    maxStackSize = size;                }                stackSize = size;            }        }        // adds the instruction to the bytecode of the method        code.put12(opcode, i.index);    }
 /**     * Adds a field reference to the constant pool of the class being build.     * Does nothing if the constant pool already contains a similar item.     *      * @param owner     *            the internal name of the field's owner class.     * @param name     *            the field's name.     * @param desc     *            the field's descriptor.     * @return a new or already existing field reference item.     */    Item newFieldItem(final String owner, final String name, final String desc) {        key3.set(FIELD, owner, name, desc);        Item result = get(key3);        if (result == null) {        //真的很聪明,这样搞得代码!!!这边用HashMap来判断,数据是否存在,这样搞真的很棒,这边常量池的问题,就解决了                    put122(FIELD, newClass(owner), newNameType(name, desc));            result = new Item(index++, key3);            put(result);        }        return result;    }


最后,把ASM解析 和修改的类,组装就可以了


/**     * Returns the bytecode of the class that was build with this class writer.     *      * @return the bytecode of the class that was build with this class writer.     */    public byte[] toByteArray() {        if (index > 0xFFFF) {            throw new RuntimeException("Class file too large!");        }        // computes the real size of the bytecode of this class        int size = 24 + 2 * interfaceCount;        int nbFields = 0;        FieldWriter fb = firstField;        while (fb != null) {            ++nbFields;            size += fb.getSize();            fb = (FieldWriter) fb.fv;        }        int nbMethods = 0;        MethodWriter mb = firstMethod;        while (mb != null) {            ++nbMethods;            size += mb.getSize();            mb = (MethodWriter) mb.mv;        }        int attributeCount = 0;        if (bootstrapMethods != null) {            // we put it as first attribute in order to improve a bit            // ClassReader.copyBootstrapMethods            ++attributeCount;            size += 8 + bootstrapMethods.length;            newUTF8("BootstrapMethods");        }        if (ClassReader.SIGNATURES && signature != 0) {            ++attributeCount;            size += 8;            newUTF8("Signature");        }        if (sourceFile != 0) {            ++attributeCount;            size += 8;            newUTF8("SourceFile");        }        if (sourceDebug != null) {            ++attributeCount;            size += sourceDebug.length + 6;            newUTF8("SourceDebugExtension");        }        if (enclosingMethodOwner != 0) {            ++attributeCount;            size += 10;            newUTF8("EnclosingMethod");        }        if ((access & Opcodes.ACC_DEPRECATED) != 0) {            ++attributeCount;            size += 6;            newUTF8("Deprecated");        }        if ((access & Opcodes.ACC_SYNTHETIC) != 0) {            if ((version & 0xFFFF) < Opcodes.V1_5                    || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {                ++attributeCount;                size += 6;                newUTF8("Synthetic");            }        }        if (innerClasses != null) {            ++attributeCount;            size += 8 + innerClasses.length;            newUTF8("InnerClasses");        }        if (ClassReader.ANNOTATIONS && anns != null) {            ++attributeCount;            size += 8 + anns.getSize();            newUTF8("RuntimeVisibleAnnotations");        }        if (ClassReader.ANNOTATIONS && ianns != null) {            ++attributeCount;            size += 8 + ianns.getSize();            newUTF8("RuntimeInvisibleAnnotations");        }        if (ClassReader.ANNOTATIONS && tanns != null) {            ++attributeCount;            size += 8 + tanns.getSize();            newUTF8("RuntimeVisibleTypeAnnotations");        }        if (ClassReader.ANNOTATIONS && itanns != null) {            ++attributeCount;            size += 8 + itanns.getSize();            newUTF8("RuntimeInvisibleTypeAnnotations");        }        if (attrs != null) {            attributeCount += attrs.getCount();            size += attrs.getSize(this, null, 0, -1, -1);        }        size += pool.length;        // allocates a byte vector of this size, in order to avoid unnecessary        // arraycopy operations in the ByteVector.enlarge() method        ByteVector out = new ByteVector(size);        out.putInt(0xCAFEBABE).putInt(version);        out.putShort(index).putByteArray(pool.data, 0, pool.length);        int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE                | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);        out.putShort(access & ~mask).putShort(name).putShort(superName);        out.putShort(interfaceCount);        for (int i = 0; i < interfaceCount; ++i) {            out.putShort(interfaces[i]);        }        out.putShort(nbFields);        fb = firstField;        while (fb != null) {            fb.put(out);            fb = (FieldWriter) fb.fv;        }        out.putShort(nbMethods);        mb = firstMethod;        while (mb != null) {            mb.put(out);            mb = (MethodWriter) mb.mv;        }        out.putShort(attributeCount);        if (bootstrapMethods != null) {            out.putShort(newUTF8("BootstrapMethods"));            out.putInt(bootstrapMethods.length + 2).putShort(                    bootstrapMethodsCount);            out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);        }        if (ClassReader.SIGNATURES && signature != 0) {            out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);        }        if (sourceFile != 0) {            out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);        }        if (sourceDebug != null) {            int len = sourceDebug.length;            out.putShort(newUTF8("SourceDebugExtension")).putInt(len);            out.putByteArray(sourceDebug.data, 0, len);        }        if (enclosingMethodOwner != 0) {            out.putShort(newUTF8("EnclosingMethod")).putInt(4);            out.putShort(enclosingMethodOwner).putShort(enclosingMethod);        }        if ((access & Opcodes.ACC_DEPRECATED) != 0) {            out.putShort(newUTF8("Deprecated")).putInt(0);        }        if ((access & Opcodes.ACC_SYNTHETIC) != 0) {            if ((version & 0xFFFF) < Opcodes.V1_5                    || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {                out.putShort(newUTF8("Synthetic")).putInt(0);            }        }        if (innerClasses != null) {            out.putShort(newUTF8("InnerClasses"));            out.putInt(innerClasses.length + 2).putShort(innerClassesCount);            out.putByteArray(innerClasses.data, 0, innerClasses.length);        }        if (ClassReader.ANNOTATIONS && anns != null) {            out.putShort(newUTF8("RuntimeVisibleAnnotations"));            anns.put(out);        }        if (ClassReader.ANNOTATIONS && ianns != null) {            out.putShort(newUTF8("RuntimeInvisibleAnnotations"));            ianns.put(out);        }        if (ClassReader.ANNOTATIONS && tanns != null) {            out.putShort(newUTF8("RuntimeVisibleTypeAnnotations"));            tanns.put(out);        }        if (ClassReader.ANNOTATIONS && itanns != null) {            out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations"));            itanns.put(out);        }        if (attrs != null) {            attrs.put(this, null, 0, -1, -1, out);        }        if (invalidFrames) {            anns = null;            ianns = null;            attrs = null;            innerClassesCount = 0;            innerClasses = null;            bootstrapMethodsCount = 0;            bootstrapMethods = null;            firstField = null;            lastField = null;            firstMethod = null;            lastMethod = null;            computeMaxs = false;            computeFrames = true;            invalidFrames = false;            new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES);            return toByteArray();        }        return out.data;    }


    

0 0