Java 如何使用ASM将布尔字段替换为常量值

Java 如何使用ASM将布尔字段替换为常量值,java,Java,我想把一个类的字段转换成一个常量。我正在使用ASM5.0.3 以下是我的测试类: public class BytecodeUtilsTest { @BeforeClass public static void beforeClass(){ String replaceFieldClassName = "com.mypackage.ClassWithFieldToReplaceWithConstant"; String replaceFieldClassNameAsPa

我想把一个类的字段转换成一个常量。我正在使用ASM5.0.3

以下是我的测试类:

public class BytecodeUtilsTest {

  @BeforeClass
  public static void beforeClass(){
    String replaceFieldClassName = "com.mypackage.ClassWithFieldToReplaceWithConstant";
    String replaceFieldClassNameAsPath = replaceFieldClassName.replace('.', '/') + ".class";
    // standard code to redefine class (inspired by ASM FAQ http://asm.ow2.org/doc/faq.html, sec. 5)
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    InputStream stream = contextClassLoader.getResourceAsStream(replaceFieldClassNameAsPath);
    byte[] classBytes;
    try {
      classBytes = IOUtils.toByteArray(stream);

      // here is the interesting part
      byte[] patchedClassBytes = BytecodeUtils.patch(classBytes, "_fieldToReplace", true);

      Reflection.invoke(contextClassLoader, "defineClass", Class.class,
              new Class[]{String.class, byte[].class, int.class, int.class},
              new Object[]{replaceFieldClassName, patchedClassBytes, 0, patchedClassBytes.length});
    } catch (IOException e) {
      throw new RuntimeException(e);
    }    
  }

  @Test
  public void testFieldReplace(){
    Assert.assertTrue(new ClassWithFieldToReplaceWithConstant().getFieldToReplace());
    Assert.assertTrue(new ClassWithFieldToReplaceWithConstant()._fieldToReplace);
  }
}
以下是要更新的测试类:

public class ClassWithFieldToReplaceWithConstant {
  boolean _fieldToReplace;

  public boolean getFieldToReplace() {
    return _fieldToReplace;
  }
}
这是修补程序:

public class BytecodeUtils {
  public static byte[] patch(byte[] bytecode, final String fieldToReplace, final boolean value) {
    ClassReader classReader = new ClassReader(bytecode);
    final ClassWriter classWriter = new ClassWriter(classReader, 0);
    ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM4, classWriter) {
      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        return new MethodVisitor(Opcodes.ASM4, super.visitMethod(access, name, desc, signature, exceptions)) {
          @Override
          public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            if (opcode == Opcodes.GETFIELD && name.equals(fieldToReplace)) {
              mv.visitInsn(Opcodes.POP);
              mv.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
            } else {
              super.visitFieldInsn(opcode, owner, name, desc);
            }
          }
        };
      }
    };
    classReader.accept(classVisitor, 0);
    return classWriter.toByteArray();
  }    
}
问题是测试在第二次断言时失败。所以,若我使用getter,它将按预期返回
true
,但若我直接读取字段,它将返回
false
。考虑到getter产生
INVOKEVIRTUAL
指令,字段读取产生
GETFIELD
,并通过调用
visitInsn
方法进行更新,这是非常意外的


我做错了什么,以及如何使直接字段访问返回
true

对于第二个断言,您需要修补ByteCodeUtilTest,而不是ClassWithFieldToReplaceWithConstant,因为第二种情况下读取字段的字节码指令实际上在ByteCodeUtilTest#testFieldReplace方法中

Getter case工作正常,因为读取字段的指令位于Getter主体内部,即位于ClassWithFieldToReplaceWithConstant类内部


如果在真实场景中字段是私有的,那么这段代码应该可以(因为在中声明的类字段之外将无法访问该字段)。否则,您必须修补每个读取或写入此字段的类。

您必须修补发生GETFIELD访问的using类。你不能将字段设置为true并将其设置为final吗?似乎没有处理过的setter。@JoopEggen“您必须在GETFIELD访问发生的地方修补using类”-是的,谢谢。我忘记了那个简单的想法。你是对的,我能做的最接近的事情就是用另一个字段替换一个字段,这个字段具有我需要的默认值。