Chr*_*ann 5 java assembly bytecode bytecode-manipulation java-bytecode-asm
我想使用ASM将静态最终字段添加到.class文件中,源文件是
public class Example {
public Example(int code) {
this.code = code;
}
public int getCode() {
return code;
}
private final int code;
}
Run Code Online (Sandbox Code Playgroud)
并且生成的反编译类应该是这样的:
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;
}
Run Code Online (Sandbox Code Playgroud)
作为结论,我想使用ASM将FIRST和SECOND常量添加到.class文件中,我该怎么办?
Jon*_*lin 20
这个答案展示了如何使用ASM的访问者api(参见ASM主页上的ASM 4.0 A Java字节码工程库的 2.2节)来完成它,因为它对我来说是最熟悉的api.ASM还有一个对象模型api(参见同一文档中的第II部分)变体,在这种情况下通常更容易使用.对象模型可能有点慢,因为它在内存中构造了整个类文件的树,但如果只有少量类需要转换,则性能命中应该可以忽略不计.
当创建static final值不是常量(如数字)的字段时,它们的初始化实际上转到" 静态初始化块 ".因此,您的第二个(转换的)代码清单等效于以下java代码:
public class Example {
public static final Example FIRST;
public static final Example SECOND;
static {
FIRST = new Example(1);
SECOND = new Example(2);
}
...
}
Run Code Online (Sandbox Code Playgroud)
在java文件中,您可以拥有多个这样的静态{...}块,而在类文件中只能有一个.java编译器自动将多个静态块合并为一个以满足此要求.当处理字节码,这意味着如果从在此之前,我们创建一个新的,而如果已经有一个静态块中,我们需要预先设置我们的代码到现有的开始(预谋不是追加更容易)没有静态块.
使用ASM,静态块看起来像一个带有特殊名称的静态方法<clinit>,就像构造函数看起来像具有特殊名称的方法一样<init>.
使用visitor api时,了解之前是否定义了方法的方法是监听所有visitMethod()调用并检查每次调用中的方法名称.在访问了所有方法之后,调用了visitEnd()方法,因此如果当时没有访问过任何方法,我们就知道需要创建一个新方法.
假设我们有一个byte []格式的orignal类,请求的转换可以像这样完成:
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;
}
Run Code Online (Sandbox Code Playgroud)
由于您的问题未能进一步说明您的最终目标是什么,因此我决定使用硬编码的基本示例来处理您的Example类案例.如果要创建要转换的类的实例,则必须更改上面包含"Example"的所有字符串,以使用实际转换的类的完整类名.或者,如果您在每个转换类中特别需要Example类的两个实例,则上面的示例按原样工作.