Kaarthik
Kaarthik

Reputation: 627

How to define a new method inside an existing class and add a call to it in an existing method inside the same class using bytebuddy?

I have a class which looks like below

public class HelloWorld{
  public void sayHelloWorld(){
    System.out.println("Hello World");
  }
}

Now I would like to add another method to the HelloWorld class using bytebuddy and add a call to the new method in sayHelloWorld. So hypothetically the class would look like this after bytebuddy does it's magic. (I know that bytebuddy works with bytecode and not java source files. The below code is just for illustration purpose.)

public class HelloWorld{
  public void sayHelloWorld(){
    System.out.println("Hello World");
    sayHelloAgain()
  }
  public void sayHelloAgain(){
    System.out.println("Hello Again")
  }
}
  1. Firstly, is this possible with bytebuddy?
  2. Secondly, if it is possible, how can I do it? I have understood that bytebuddy can be used to redefine methods, but not modify the method body. Is this true?

It would be great if someone could shed some light on this. TIA!

Upvotes: 3

Views: 3833

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

Byte Buddy allows you to do this in various ways. The most straight forward way would be to define an interceptor that implements sayHelloAgain to which Byte Buddy creates a delegation:

public class HelloAgainDelegate {
  public static void sayHelloAgain() {
    System.out.println("Hello again");
  }
}

You can then define the method on the redefined class and rebase the sayHelloWorld method to first invoke the original method and then invoke the other method:

Class<?> type = new ByteBuddy()
  .rebase(HelloWorld.class)
  .defineMethod("sayHelloAgain", void.class, Visibility.PUBLIC)
  .intercept(MethodDelegation.to(HelloAgainDelegate.class))
  .method(named("sayHelloWorld"))
  .intercept(SuperMethodCall.INSTANCE
    .andThen(MethodCall.invoke(named("sayHelloAgain"))))
  .make()
  .load(HelloWorld.class.getClassLoader(), 
        ClassLoadingStrategy.Default.CHILD_FIRST)
  .getLoaded();

Object instance = type.newInstance();
type.getMethod("sayHelloWorld").invoke(instance);
type.getMethod("sayHelloAgain").invoke(instance);

In Java code, the rebased class would look something like this:

public class HelloWorld {
  synthetic void sayHelloWorld$origin() {
    System.out.println("Hello World");
  }  

  public void sayHelloWorld() {
    sayHelloWorld$origin();
    sayHelloAgain();
  }

  public void sayHelloAgain() {
    HelloAgainInterceptor.sayHelloAgain();
  }
}

If this delegation is not an option for you, you can also use Advice to inline the code from a template class:

class HelloAgainAdvice {
  @Advice.OnMethodExit
  static void sayHelloAgain() {
    System.out.println("Hello again");
  }
}

Instead of the MethodDelegation, you would use this class via Advice.to(HelloAgainAdvice.class). As the code is copied, you will not be able to set break points but your redefined class will be self-contained.

Upvotes: 6

Related Questions