Charlee Chitsuk
Charlee Chitsuk

Reputation: 9059

How to apply, remove and re-apply the bytebuddy transformation?

I'm trying to apply, remove and re-apply the bytebuddy transformation via the javaagent as the following code. The remove is worked properly via the ResettableClassFileTransformer::reset, but the re-apply is no worked.

// The target classes

public @interface ToString {
}

public interface Greetable {
    String say(final String name);
}

@ToString
public class Greeter implements Greetable {
     public String say(final String name) {
         return "Hello " + name;
     }
}
//note the some transform is from the example
new AgentBuilder.Transformer() {
  @Override
  public DynamicType.Builder transform(DynamicType.Builder builder,
                                       TypeDescription     typeDescription,
                                       ClassLoader         classloader,
                                       JavaModule          module) {
    return builder.method(named("say"))
                  .intercept(FixedValue.value("transformed");
  }
})
// The transformation
public class MyProgram {
    public static void main(final String[] args) {
        ByteBuddyAgent.install();
        AgentBuilder bldr = new AgentBuilder.Default().
                                             with(AgentBuilder.Listener.
                                                      StreamWriting.
                                                      toSystemOut()).
                                             with(RedefinitionStrategy.
                                                     RETRANSFORMATION).
                                             disableClassFormatChanges().
                                             type(isSubTypeOf(
                                                       Greetable.class)).
                                             transform(<some transform>);
        ResettableClassFileTransformer resetter = 
                                          bldr.installOnByteBuddyAgent();
        Greetable g1 = new Greeter();
        System.out.println(g1.say("001"));

        //remove the transformation
        resetter.reset(ByteBuddyAgent.getInstrumentation(),
                       RedefinitionStrategy.RETRANSFORMATION);
        Greetable g2 = new Greeter();
        System.out.println(g2.say("002"));

        //re-apply the transformation
        bldr.installOn(ByteBuddyAgent.getInstrumentation());
        Greetable g3 = new Greeter();
        System.out.println(g3.say("003"));

    }
}

The output is

TRANSFORM Greetable [sun.misc.Launcher$AppClassLoader@7ab2bfe1,
                     null,
                     loaded=true]
TRANSFORM Greeter [sun.misc.Launcher$AppClassLoader@7ab2bfe1,
                   null,
                   loaded=true]
transformed //note: the g1.say() is transformed.

Hello 002         //note: the g2 is an orginal

TRANSFORM Greetable [sun.misc.Launcher$AppClassLoader@7ab2bfe1,
                     null,
                     loaded=true]
TRANSFORM Greeter [sun.misc.Launcher$AppClassLoader@7ab2bfe1,
                   null,
                   loaded=true]
Hello 003         //note the re-apply has not worked.

Could you please help to advise how to achieve the re-apply?

EDIT1

I've updated the byte-buddy to version 1.6.11 and changed the AgentBuilder.Transformer. The result still same. The g3.say() is not transformed.

My environment is

Windows 10 64Bits

java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

EDIT2

I've changed the type matcher from interface

type(isSubTypeOf(Greetable.class))

to concrete class as

type(isSubTypeOf(Greeter.class))

The g3.say() is transformed propery. Is there any limitation on interface matching and transforming?

Upvotes: 2

Views: 1843

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

You are running an old version of Byte Buddy as I can tell from the AgentBuilder.Transformer API. I just tried with a recent version and it works.

In practice, do you rely on the @ToString annotation for triggering the transformation? When you transform a class that is not yet loaded, Byte Buddy reads annotations from the class file directly. For a loaded class, it uses the loaded representation. Your annotation is not annotated with a @RetentionPolicy(Retention.RUNTIME) to retain the annotations visibility which might be the problem.

UPDATE: You need to refine your method matcher by excluding the say method from the interface by not(isAbstract()).and(named("say")). Otherwise, you include the interface in your retransformation attempt. The JVM seems to silently fail the second retransformation because of this as you cannot change an abstract method into a default method after the class was loaded. Normally, this triggers a exception but in your case, the VM seems to silently suppress the error.

Upvotes: 3

Related Questions