【问题标题】:renaming a field using javassist at runtime in the pre-main method (java instrumentation)在 pre-main 方法中使用 javassist 在运行时重命名字段(java 检测)
【发布时间】:2014-11-04 14:09:46
【问题描述】:

我想在运行时重命名 java 类中的字段。此外,访问该字段的任何方法;无论是读取还是写入;我需要修改它以使用新名称而不是旧名称....

所有这些都将在 pre-main 方法中完成...

作为一个例子,给出以下代码:

public class Class1
{
    String strCompany;

    public String Test()
    {
         strCompany = "TestCompany";
         return strCompany;
    }
}

在上面的类中,我需要将字段“strCompany”更改为“strCompany2”,另外我需要方法Test使用新名称而不是旧名称....

可以使用 ctField 类中的 setName 方法更改字段名称,但是如何修改方法主体以使用新名称。

【问题讨论】:

  • 看看 Javassist 的字节码级 API。但是,我建议您为此使用 ASM 之类的工具,这可能更合适。
  • @raphw 实际上 javassist 允许您在不需要低级 api 的情况下做到这一点

标签: java instrumentation javassist


【解决方案1】:

好吧,我回答得晚了,但我希望你仍然觉得它有用(或者至少其他人需要这种东西)。

即使您可以使用像 Raphw 在评论中建议的低级字节码 API,javassist 确实允许您使用高级 API(我推荐)来执行此操作。

我将在下面介绍的解决方案将更改字段名称并将 所有 引用从旧字段名称更改为新字段名称,这可能是您想要的,因为您是重命名字段。

代码

让我们使用您的 Class1 示例。

  ClassPool classpool = ClassPool.getDefault();
  CtClass ctClass = classpool.get(Class1.class.getName());
  CtField field = ctClass.getField("strCompany");
  CodeConverter codeConverter = new CodeConverter();
  codeConverter.redirectFieldAccess(field, ctClass, "strCompany2");
  ctClass.instrument(codeConverter);

  field.setName("strCompany2");
  ctClass.writeFile("./injectedClasses");

我假设访问 CtField 并设置其名称 - 由于您的问题 - 您已经知道该怎么做。 关于“重新布线”所有字段引用的技巧是使用CodeConverter 完成的 这将替换对 CtField field 的所有引用,以替换对 ctClass 中名为 strCompany2 的字段的引用(恰好是同一个类)。请记住,这需要将该字段重命名为 strCompany2 之前完成。

在此运行结束时,您将在 injectionClasses 文件夹中准备好新的 Class1,以便使用 strCompany2 而不是 strCompany。 :-)

旁注

请记住,CodeConverter 真正做的是在类常量池中创建一个新条目,并将所有引用从有关旧字段的条目重新路由到定义“新”(读取重命名)字段的条目。

所以在 Class1 示例中,会发生以下情况:

注入前的常量池

Constant pool:
#1 = Class              #2             //  test/Class1
#2 = Utf8               test/Class1
#3 = Class              #4             //  java/lang/Object
#4 = Utf8               java/lang/Object
#5 = Utf8               strCompany
#6 = Utf8               Ljava/lang/String;
#7 = Utf8               <init>
#8 = Utf8               ()V
#9 = Utf8               Code
#10 = Methodref          #3.#11         //  java/lang/Object."<init>":()V
#11 = NameAndType        #7:#8          //  "<init>":()V
#12 = Utf8               LineNumberTable
#13 = Utf8               LocalVariableTable
#14 = Utf8               this
#15 = Utf8               Ltest/Class1;
#16 = Utf8               test
#17 = Utf8               ()Ljava/lang/String;
#18 = String             #19            //  TestCompany
#19 = Utf8               TestCompany
#20 = Fieldref           #1.#21         // test/Class1.strCompany:Ljava/lang/String;
#21 = NameAndType        #5:#6          //  strCompany:Ljava/lang/String;
#22 = Utf8               SourceFile
#23 = Utf8               Class1.java

注入后的常量池

Constant pool:
#1 = Class              #2             //  test/Class1
#2 = Utf8               test/Class1
#3 = Class              #4             //  java/lang/Object
#4 = Utf8               java/lang/Object
#5 = Utf8               strCompany
#6 = Utf8               Ljava/lang/String;
#7 = Utf8               <init>
#8 = Utf8               ()V
#9 = Utf8               Code
#10 = Methodref          #3.#11         //  java/lang/Object."<init>":()V
#11 = NameAndType        #7:#8          //  "<init>":()V
#12 = Utf8               LineNumberTable
#13 = Utf8               LocalVariableTable
#14 = Utf8               this
#15 = Utf8               Ltest/Class1;
#16 = Utf8               test
#17 = Utf8               ()Ljava/lang/String;
#18 = String             #19            //  TestCompany
#19 = Utf8               TestCompany
#20 = Fieldref           #1.#21         // test/Class1.strCompany:Ljava/lang/String; 
#21 = NameAndType        #5:#6          //  strCompany:Ljava/lang/String;
#22 = Utf8               SourceFile
#23 = Utf8               Class1.java
#24 = Utf8               strCompany2
#25 = NameAndType        #24:#6         //  strCompany2:Ljava/lang/String;
#26 = Fieldref           #1.#25         //test/Class1.strCompany2:Ljava/lang/String;

在这种情况下,用一个字段重写你的 constantPool 增长了 3 帧,代表新字段的定义。通常这不是问题,但我宁愿提前提及。

【讨论】:

  • 非常感谢这么详细的回答,其实我多次尝试使用codeconverter,唯一的错误是我在instrumentation之前更改了字段名。
  • pabrantes,如果我在 Transformer 类中的 transform 方法中执行上述所有操作,这可能吗,因为我在 transform 方法中调用了 instrument 方法......
  • @ManKeer:通常,Transformer 使用低级 API 通过 CodeIterator 修改 CodeAttribute。尽管如此,由于您还拥有更高级别的变量(例如 CtClass 和 CtField),您可能能够摆脱它......但根据我的个人经验,我只建议在操作码级别操作时使用 Transformers。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多