Add method annotation at runtime with Byte Buddy

2020-04-12 01:11发布

问题:

I've been searching for the answer to "how to add an annotation to the method at runtime" for several days already and found this awesome tool called Byte Buddy, played with it, but still cannot make it work as I need to. I'm sure it must be able to do that from this question Can Byte Buddy create fields and method annotations at runtime?

Having this class:

public class ClassThatNeedsToBeAnnotated {
  public void method(int arg1, String arg2) {
    // code that we don't want to touch at all, leave it as is
    System.out.println("Called method with arguments " + arg1 + " " + arg2);
  }

  public void method() {
    System.out.println("Called method without arguments");
  }
}

and this code:

 public class MainClass {
      public static void main(String[] args) {
        ByteBuddyAgent.install();

        AnnotationDescription description = AnnotationDescription.Builder.ofType(MyClassAnnotation.class)
            .define("value", "new")
            .build();

        new ByteBuddy()
            .redefine(ClassThatNeedsToBeAnnotated.class)
            .annotateType(description)
            .make()
            .load(ClassThatNeedsToBeAnnotated.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

      }
}

It is easy to add an annotation to the class. But for the method, it seems to be not possible without changing the method implementation.

Method existingMethod = ClassThatNeedsToBeAnnotated.class.getDeclaredMethods()[0];
AnnotationDescription annotationDescription = AnnotationDescription.Builder.ofType(MyMethodAnnotation.class)
    .build();
new ByteBuddy()
    .redefine(ClassThatNeedsToBeAnnotated.class)
    .annotateType(description)
    .method(ElementMatchers.anyOf(existingMethod))
    // here I don't want to intercept method, I want to leave the method code untouched. How to do that?
    .annotateMethod(annotationDescription)
    .make()
    .load(ClassThatNeedsToBeAnnotated.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

I'm sure that I just don't do it right but unfortunately cannot find an example when there is no code change for the method and only annotation change.

回答1:

You found a blindspot in Byte Buddy that I thought of fixing for a while. Early versions of Byte Buddy did not allow for defining annotations but when it did, the API was already so widely used that I could not change it and it would require some bits in the implementation, too.

If you are willing to pay the minimal price of adding a synthetic method, you can rebase the class instead:

new ByteBuddy().rebase(ClassThatNeedsToBeAnnotated.class)

Doing so, you can just use the current API and add an implementation of SuperMethodCall. This will invoke the very same method in a rebasement.

This enhancement of Byte Buddy is tracked here: https://github.com/raphw/byte-buddy/issues/627

UPDATE: In the upcoming Byte Buddy 1.10.0 this is possible by:

new ByteBuddy()
  .redefine(ClassThatNeedsToBeAnnotated.class)
  .visit(new MemberAttributeExtension.ForMethod()
    .annotateMethod(someAnnotation)
    .on(matcher))
  .make();

Annotation instance can be get by: AnnotationDescription.Latent.Builder.ofType(AnnotationClass.class).build()