【问题标题】:How to add static final field with initializer using ASM?如何使用 ASM 添加带有初始化程序的静态最终字段?
【发布时间】:2012-06-16 16:42:02
【问题描述】:

我想使用ASM将静态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 文件中,我该怎么做?

【问题讨论】:

  • 这是java吗?这个问题与 manen-assembly-plugin 有关吗?然后将其标记为这样。

标签: java bytecode java-bytecode-asm bytecode-manipulation


【解决方案1】:

这个答案展示了如何使用 ASM 的访问者 api 来完成它(参见 ASM homepage 上的 ASM 4.0 A Java 字节码工程库的第 2.2 节),因为它是最熟悉的 api我。 ASM 也有一个对象模型 api(参见同一文档中的第二部分)变体,在这种情况下通常可能更容易使用。对象模型可能会慢一些,因为它在内存中构建了整个类文件的树,但如果只有少量类需要转换,性能损失应该可以忽略不计。

当创建值不是常量(如数字)的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> 的方法。

使用visitor 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 类的实例,则上面的示例将按原样工作。

【讨论】:

  • 希望我能给这个答案 10 票。使用 ASM 添加/删除方法很容易。这个答案显示了修改它们的关键技术。
猜你喜欢
  • 2014-10-06
  • 2019-11-13
  • 1970-01-01
  • 2010-12-24
  • 2011-02-23
  • 1970-01-01
  • 2013-07-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多