dimanche 23 janvier 2022

Insert code into a method - Java JDK17, javassist [closed]

So I wanted to modify the code on-the-fly, and found this question with this answer. This is exactly what I needed, beside of being 12 years old and not working in JDK17 (well... reality of 2022).

I am bit green on Deep Reflection in Java, so it took me a while to even make this code working Today. If you feel the same about yourself, let me save you a couple of hours with this short introduction.

In JDK16 (and above) it is illegal to have fun (as is well explained here)...

...unless you run your program with:

--add-opens java.base/java.lang=ALL-UNNAMED
(You may or may not need this parameter, just keep it in mind)

And what is in java.base you can read here.

Now having some chance to run it, let see the code

In short, the code adds a Set that is updated every time the setter is called. Before you read the code, as being as green as me, I recommend you to read the comments first.

Comments

First and foremost: DO NOT load the class before modifications! rewritePersonClass(); is the first thing that happens in the code. When you load a class the game is over! (a sort of). So no new Person(), not even Person.class, in the code. When the class is loaded, only the GC (Garbage Collector) can get rid of it. That means, the modified class cannot replace it (at least not with the current ClassLoader). This answer, this and this may be useful while changing the ClassLoader.

Look here: CtClass ctPerson = pool.get("Person"); It is so tempting to write Person.class.getName(). But what did I write above? No class touching!

So the class is modified, loader did not see it before, let's load the new class. This line ctPerson.toClass(); is where the modified class should be loaded. But it is not working anymore (as of javassist 3.28.0-GA).

To make it working, you need to use: ctEntity.toClass(Class<?> neighbor);. Sweet, but who is the neighbor? You may ask.

The neighbor is a class that lives in the same namespace as the class you modify, but of course, you must not care about it, as it will be loaded (see above).

public class Neighbor // the Neighbor of Person...
{
}

So finally we can move on: ctEntity.toClass(Neighbor.class);

The code (Copied from the answer)

public class Person {

    private String firstName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

public static void rewritePersonClass() throws NotFoundException, CannotCompileException {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctPerson = pool.get("Person");
    CtClass ctSet = pool.get("java.util.LinkedHashSet");

    CtField setField = new CtField(ctSet, "updatedFields", ctPerson);
    ctPerson.addField(setField, "new java.util.LinkedHashSet();");

    CtMethod method = ctPerson.getDeclaredMethod("setFirstName");
    method.insertBefore("updatedFields.add(\"firstName\");");

    ctPerson.toClass();
}


public static void main(String[] args) throws Exception {
    rewritePersonClass();

    Person p = new Person();
    p.setFirstName("foo");

    Field field = Person.class.getDeclaredField("updatedFields");
    field.setAccessible(true);
    Set<?> s = (Set<?>) field.get(p);

    System.out.println(s);
}

I hope it worked for you and gave you a kick-start into Java's Deep Reflection.





Aucun commentaire:

Enregistrer un commentaire