我想使用ASM将static final字段添加到.class文件中,并且源文件为
public class Example {
public Example(int code) {
this.code = code;
}
public int getCode() {
return code;
}
private final int code;
}
和生成的反编译类应该是这样的:
public class Example {
public static final Example FIRST = new Example(1);
public static final Example SECOND = new Example(2);
public Example(int code) {
this.code = code;
}
public int getCode() {
return code;
}
private final int code;
}
作为结论,我想使用ASM将FIRST和SECOND常量添加到.class文件,该怎么办?
此答案显示了如何使用ASM的访问者api(请参阅ASM homepage上的ASM 4.0 A Java字节码工程库]的2.2部分完成此操作,因为它是我最熟悉的api。 ASM还具有对象模型api(请参见同一文档的第II部分)变体,这种变体在这种情况下通常更易于使用。对象模型可能会慢一些,因为它在内存中构造了整个类文件的树,但是如果只有少量类需要转换,那么性能影响应该可以忽略不计。
当创建其值不是常数(如数字)的static final
字段时,它们的初始化实际上是进入“ static initializer block”的位置。因此,您的第二个(转换后的)代码清单等效于以下Java代码:
public class Example {
public static final Example FIRST;
public static final Example SECOND;
static {
FIRST = new Example(1);
SECOND = new Example(2);
}
...
}
在一个Java文件中,可以有多个这样的静态{...}块,而在类文件中,只能有一个。 Java编译器自动将多个静态块合并为一个,以满足这一要求。在处理字节码时,这意味着如果之前没有静态块,那么我们将创建一个新的静态块,而如果已经存在一个静态块,则需要将我们的代码添加到现有代码的开头(比添加容易)。 >
[使用ASM,静态块看起来像是具有特殊名称<clinit>
的静态方法,就像构造函数看起来像是具有特殊名称<init>
的方法。
[使用访问者api时,要知道以前是否已定义方法,方法是侦听所有visitMethod()调用并检查每个调用中的方法名称。在访问完所有方法之后,将调用visitEnd()方法,因此,如果届时还没有访问过任何方法,我们知道我们需要创建一个新方法。
假设我们有一个byte []格式的原始类,可以像这样完成所需的转换:
import org.objectweb.asm.*; import static org.objectweb.asm.Opcodes.*; public static byte[] transform(byte[] origClassData) throws Exception { ClassReader cr = new ClassReader(origClassData); final ClassWriter cw = new ClassWriter(cr, Opcodes.ASM4); // add the static final fields cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "FIRST", "LExample;", null, null).visitEnd(); cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "SECOND", "LExample;", null, null).visitEnd(); // wrap the ClassWriter with a ClassVisitor that adds the static block to // initialize the above fields ClassVisitor cv = new ClassVisitor(ASM4, cw) { boolean visitedStaticBlock = false; class StaticBlockMethodVisitor extends MethodVisitor { StaticBlockMethodVisitor(MethodVisitor mv) { super(ASM4, mv); } public void visitCode() { super.visitCode(); // here we do what the static block in the java code // above does i.e. initialize the FIRST and SECOND // fields // create first instance super.visitTypeInsn(NEW, "Example"); super.visitInsn(DUP); super.visitInsn(ICONST_1); // pass argument 1 to constructor super.visitMethodInsn(INVOKESPECIAL, "Example", "<init>", "(I)V"); // store it in the field super.visitFieldInsn(PUTSTATIC, "Example", "FIRST", "LExample;"); // create second instance super.visitTypeInsn(NEW, "Example"); super.visitInsn(DUP); super.visitInsn(ICONST_2); // pass argument 2 to constructor super.visitMethodInsn(INVOKESPECIAL, "Example", "<init>", "(I)V"); super.visitFieldInsn(PUTSTATIC, "Example", "SECOND", "LExample;"); // NOTE: remember not to put a RETURN instruction // here, since execution should continue } public void visitMaxs(int maxStack, int maxLocals) { // The values 3 and 0 come from the fact that our instance // creation uses 3 stack slots to construct the instances // above and 0 local variables. final int ourMaxStack = 3; final int ourMaxLocals = 0; // now, instead of just passing original or our own // visitMaxs numbers to super, we instead calculate // the maximum values for both. super.visitMaxs(Math.max(ourMaxStack, maxStack), Math.max(ourMaxLocals, maxLocals)); } } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (cv == null) { return null; } MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if ("<clinit>".equals(name) && !visitedStaticBlock) { visitedStaticBlock = true; return new StaticBlockMethodVisitor(mv); } else { return mv; } } public void visitEnd() { // All methods visited. If static block was not // encountered, add a new one. if (!visitedStaticBlock) { // Create an empty static block and let our method // visitor modify it the same way it modifies an // existing static block MethodVisitor mv = super.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); mv = new StaticBlockMethodVisitor(mv); mv.visitCode(); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } super.visitEnd(); } }; // feed the original class to the wrapped ClassVisitor cr.accept(cv, 0); // produce the modified class byte[] newClassData = cw.toByteArray(); return newClassData; }
由于您的问题并未进一步说明您的最终目标是什么,所以我决定采用一个经过硬编码的基本示例,以用于您的Example类案例。如果要创建要转换的类的实例,则必须更改所有包含上述“ Example”的字符串,以使用实际要转换的类的完整类名。或者,如果您特别希望每个转换的类中都有Example类的两个实例,则上面的示例将按原样工作。