Xavier Coulon
Xavier Coulon

Reputation: 1600

Redefining a method overrides previous redefinitions

I'm trying to redefine 2 methods in a Bar class using the following code:

private <T> Class<? extends T> replaceMethodInClass(final Class<T> subclass,
    final ClassFileLocator classFileLocator, final String methodName) {
  final Builder<? extends T> classBuilder =
      this.bytebuddy.redefine(subclass, classFileLocator);
  return classBuilder
      .method(ElementMatchers.named(methodName))
      .intercept(MethodDelegation.to(CustomInterceptor.class))
      .make()
      .load(ByteBuddyReplaceMethodInClassTest.class.getClassLoader(),
          ClassReloadingStrategy.fromInstalledAgent())
      .getLoaded();
}

where the CustomInterceptor class is as follow:

static class CustomInterceptor {
  public static String intercept() {
    return "Hello!";
  }
}

In my test, I do the following to redefine the Bar#sayHello() and Bar#sayHelloAgain() methods:

@Test
public void shouldReplaceTwoMethodsFromClass_instanciateAfterChanges()
    throws InstantiationException, IllegalAccessException, Exception {
  // given
  replaceMethodInClass(Bar.class, ClassFileLocator.ForClassLoader.of(Bar.class.getClassLoader()),
      "sayHello");
  replaceMethodInClass(Bar.class, ClassFileLocator.ForClassLoader.of(Bar.class.getClassLoader()),
      "sayHelloAgain");
  // when
  final Bar instance = Bar.class.newInstance();
  final String hello = instance.sayHello();
  final String helloAgain = instance.sayHelloAgain();
  // then
  assertThat(hello).isEqualTo("Hello!");
  assertThat(helloAgain).isEqualTo("Hello!");
}

Note that I explicitly want to replace the methods one by one. The test fails because the hello variable is null (that's the value returned by the Bar#sayHello() method in the Bar class), but the helloAgain variable is set to Hello!, as expected (the CustomInterceptor class was used). So it seems like the first method redefinition was erased when doing the second one.

Do you have any idea what happened and how I can keep the 2 method redefinitions while not loosing the first one ?

Upvotes: 2

Views: 92

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

It all depends on the ClassFileLocator you are using. You are using a class file locator that is querying a class loader. A class loader always returns the class file that was originally available from the class file and is not aware of any transformations.

If you want to retain the changes of your first transformation, you would need to build some form of memory into your class file locator to return the changed class file. You can read the bytes generated by a transformation from dynamicType.getBytes().

You can also look up a live class file using the Java instrumentation API by ClassFileLocator.AgentBased which does however require attaching a Java agent.

Upvotes: 1

Related Questions