• ASM:(6)FieldVisitor和FieldWriter


    原文:https://lsieun.github.io/java-asm-01/field-visitor-intro.html

    FieldVisitor介绍

    通过调用ClassVisitor类的visitField()方法,会返回一个FieldVisitor类型的对象。

    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value);
    

    在本文当中,我们将对FieldVisitor类进行介绍:

    在学习FieldVisitor类的时候,可以与ClassVisitor类进行对比,这两个类在结构上有很大的相似性:两者都是抽象类,都定义了两个字段,都定义了两个构造方法,都定义了visitXxx()方法。

    class info

    第一个部分,FieldVisitor类是一个abstract类。

    public abstract class FieldVisitor {
    }
    

    fields

    第二个部分,FieldVisitor类定义的字段有哪些。

    public abstract class FieldVisitor {
        protected final int api;
        protected FieldVisitor fv;
    }
    

    constructors

    第三个部分,FieldVisitor类定义的构造方法有哪些。

    public abstract class FieldVisitor {
        public FieldVisitor(final int api) {
            this(api, null);
        }
    
        public FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
            this.api = api;
            this.fv = fieldVisitor;
        }
    }
    

    methods

    第四个部分,FieldVisitor类定义的方法有哪些。

    FieldVisitor类当中,一共定义了4个visitXxx()方法,但是,我们只需要关注其中的visitEnd()方法就可以了。

    我们为什么只关注visitEnd()方法呢?因为我们刚开始学习ASM,有许多东西不太熟悉,为了减少我们的学习和认知“负担”,那么对于一些非必要的方法,我们就暂时忽略它;将visitXxx()方法精简到一个最小的认知集合,那么就只剩下visitEnd()方法了。

    public abstract class FieldVisitor {
        // ......
    
        public void visitEnd() {
            if (fv != null) {
                fv.visitEnd();
            }
        }
    }
    

    另外,在FieldVisitor类内定义的多个visitXxx()方法,也需要遵循一定的调用顺序,如下所示:

    (
     visitAnnotation |
     visitTypeAnnotation |
     visitAttribute
    )*
    visitEnd
    

    由于我们只关注visitEnd()方法,那么,这个调用顺序就变成如下这样:

    visitEnd
    

    FieldVisitor类示例

    示例一:字段常量

    预期目标

    public interface HelloWorld {
        int intValue = 100;
        String strValue = "ABC";
    }
    

    编码实现

    public class HelloWorldGenerateCore implements Opcodes {
    
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
            // (1) 创建ClassWriter对象
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            // (2) 调用visitXxx()方法
            cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "sample/HelloWorld", null, "java/lang/Object", null);
            {
                FieldVisitor fv1 = cw.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "intValue", "I", null, 100);
                fv1.visitEnd();
            }
            {
                FieldVisitor fv2 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "strValue", "Ljava/lang/String;", null, "ABC");
                fv2.visitEnd();
            }
            cw.visitEnd();
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    运行结果:

    image-20220412141528343

    FieldWriter介绍

    FieldWriter类继承自FieldVisitor类。在ClassWriter类里,visitField()方法的实现就是通过FieldWriter类来实现的。

    class info

    第一个部分,FieldWriter类的父类是FieldVisitor类。需要注意的是,FieldWriter类并不带有public修饰,因此它的有效访问范围只局限于它所处的package当中,不能像其它的public类一样被外部所使用。

    final class FieldWriter extends FieldVisitor {
    }
    

    fields

    第二个部分,FieldWriter类定义的字段有哪些。在FieldWriter类当中,一些字段如下:

    final class FieldWriter extends FieldVisitor {
        private final int accessFlags;
        private final int nameIndex;
        private final int descriptorIndex;
        private Attribute firstAttribute;
    }
    

    这些字段与ClassFile当中的field_info是对应的:

    field_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    constructors

    第三个部分,FieldWriter类定义的构造方法有哪些。在FieldWriter类当中,只定义了一个构造方法;同时,它也不带有public标识,只能在package内使用。

    final class FieldWriter extends FieldVisitor {
        FieldWriter(SymbolTable symbolTable, int access, String name, String descriptor, String signature, Object constantValue) {
            super(Opcodes.ASM9);
            this.symbolTable = symbolTable;
            this.accessFlags = access;
            this.nameIndex = symbolTable.addConstantUtf8(name);
            this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
            if (signature != null) {
                this.signatureIndex = symbolTable.addConstantUtf8(signature);
            }
            if (constantValue != null) {
                this.constantValueIndex = symbolTable.addConstant(constantValue).index;
            }
        }
    }
    

    methods

    第四个部分,FieldWriter类定义的方法有哪些。在FieldWriter类当中,有两个重要的方法:computeFieldInfoSize()putFieldInfo()方法。这两个方法会在ClassWriter类的toByteArray()方法内使用到。

    final class FieldWriter extends FieldVisitor {
        int computeFieldInfoSize() {
            // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes.
            int size = 8;
            // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
            if (constantValueIndex != 0) {
                // ConstantValue attributes always use 8 bytes.
                symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE);
                size += 8;
            }
            // ......
            return size;
        }
    
        void putFieldInfo(final ByteVector output) {
            boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
            // Put the access_flags, name_index and descriptor_index fields.
            int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
            output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
            // Compute and put the attributes_count field.
            // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
            int attributesCount = 0;
            if (constantValueIndex != 0) {
                ++attributesCount;
            }
            // ......
            output.putShort(attributesCount);
            // Put the field_info attributes.
            // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
            if (constantValueIndex != 0) {
                output
                  .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE))
                  .putInt(2)
                  .putShort(constantValueIndex);
            }
            // ......
        }
    }
    

    FieldWriter类的使用

    关于FieldWriter类的使用,它主要出现在ClassWriter类当中的visitField()toByteArray()方法内。

    visitField方法

    ClassWriter类当中,visitField()方法代码如下:

    public class ClassWriter extends ClassVisitor {
        public final FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
            FieldWriter fieldWriter = new FieldWriter(symbolTable, access, name, descriptor, signature, value);
            if (firstField == null) {
                firstField = fieldWriter;
            } else {
                lastField.fv = fieldWriter;
            }
            return lastField = fieldWriter;
        }
    }
    

    toByteArray方法

    ClassWriter类当中,toByteArray()方法代码如下:

    public class ClassWriter extends ClassVisitor {
        public byte[] toByteArray() {
    
            // First step: compute the size in bytes of the ClassFile structure.
            // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version,
            // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count,
            // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too.
            int size = 24 + 2 * interfaceCount;
            int fieldsCount = 0;
            FieldWriter fieldWriter = firstField;
            while (fieldWriter != null) {
                ++fieldsCount;
                size += fieldWriter.computeFieldInfoSize();    // 这里是对FieldWriter.computeFieldInfoSize()方法的调用
                fieldWriter = (FieldWriter) fieldWriter.fv;
            }
            // ......
    
    
            // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
            // dynamic resizes) and fill it with the ClassFile content.
            ByteVector result = new ByteVector(size);
            result.putInt(0xCAFEBABE).putInt(version);
            symbolTable.putConstantPool(result);
            int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
            result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
            result.putShort(interfaceCount);
            for (int i = 0; i < interfaceCount; ++i) {
                result.putShort(interfaces[i]);
            }
            result.putShort(fieldsCount);
            fieldWriter = firstField;
            while (fieldWriter != null) {
                fieldWriter.putFieldInfo(result);             // 这里是对FieldWriter.putFieldInfo()方法的调用
                fieldWriter = (FieldWriter) fieldWriter.fv;
            }
            // ......
    
            // Third step: replace the ASM specific instructions, if any.
            if (hasAsmInstructions) {
                return replaceAsmInstructions(result.data, hasFrames);
            } else {
                return result.data;
            }
        }
    }
    

    总结

    • 第一点,对于FieldWriter类的各个不同部分进行介绍,以便从整体上来理解FieldWriter类。
    • 第二点,关于FieldWriter类的使用,它主要出现在ClassWriter类当中的visitField()toByteArray()方法内。
    • 第三点,从ASM应用的角度来说,只需要知道FieldWriter类的存在就可以了,不需要深究,我们平常写ASM代码的时候,由于它不带有public标识,所以不会直接用到它;从理解ASM源码的角度来说,FieldWriter类则值得研究,可以重点关注一下computeFieldInfoSize()putFieldInfo()这两个方法。
  • 相关阅读:
    处理跨浏览器的事件处理程序
    html5 canvas时钟
    拖拽事件的原理
    改变top使用轮播图
    程序开发之最大子数组
    第四周学习进度
    敏捷开发相关阅读
    构建之法读书笔记04
    四则运算终结版
    第三周学习进度
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/16135281.html
Copyright © 2020-2023  润新知