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();
}
}
运行结果:
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()
这两个方法。