renaming a field using javassist at runtime in the pre-main method (java instrumentation)

谁说我不能喝 提交于 2020-01-13 09:34:32

问题


I want to rename a field inside a java class at runtime. In addition, Any method that access that field ;wether it's read or write; I need it to be modified to use the new name instead of the old name....

All this will be done inside the pre-main method...

As an Exmaple, given the following code:

public class Class1
{
    String strCompany;

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

In the above class, I need to change the field "strCompany" to be "strCompany2", in addition I need the method Test to use the new name instead of the old name....

changing the field name can be done using the setName method from the ctField class, but how can I modify the method body to use the new name.


回答1:


Well I'm a late on the answer but I hope you still find it useful (or at least someone else needing this kind of thing).

Even though you can use the low level bytecode api like Raphw suggested in the comment javassist does allow you to do this with the higher level API (which I recomend).

The solution I'll be presenting below will change the field name and will change all references from the old field name to the new one, which it's probably what you would want anyway since you're renaming the field.

The code

Let's use your Class1 example.

  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");

The access to CtField and setting its name I assume - due to your question - you already know how to do it. The trick about "rewiring" all field references is done using a CodeConverter that will replace all references to the CtField field for the references to the field named strCompany2 in ctClass (which happens to be the same class). Keep in mind that this needs to be done before renaming the field into strCompany2.

At the end of this run you'll have your newly Class1 in injectedClasses folder ready to use strCompany2 instead of strCompany. :-)

Sidenote

Keep in mind that what CodeConverter really does is create a new entry in the class Constant Pool and re-route all references from the entry regarding the old field to one that defines the "new" (read renamed) field.

So in the Class1 example, here is what happens:

Constant Pool BEFORE Injection

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 AFTER injection

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;

In this case, with a single field rewrite your constantPool grew 3 frames which represent the definition of the new field. Usually this is not an issue, but nevertheless I rather mention it upfront.



来源:https://stackoverflow.com/questions/26737226/renaming-a-field-using-javassist-at-runtime-in-the-pre-main-method-java-instrum

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!