如何使用 ByteBuddy 向 class 添加字段和操作值?

How to add field and manipulate value to a class with ByteBuddy?

我想使用 ByteBuddy 将以下代码添加到现有的 classes。给定一个现有的 class SomeSample 我想把它变成下面的:

class SomeSample {

  private @Transient boolean isNew = true;

  public boolean isNew() {
    return isNew;
  }

  @PrePersist
  @PostLoad
  void markNotNew() {
    this.isNew = false;
  }
}

最初的尝试

我可以正确添加字段和方法。我无法真正开始工作的是价值的分配。我了解到我需要扩充 class 的所有现有构造函数我想扩充技术上像这样声明的赋值被编译到构造函数中。

我创建了以下助手:

public class Helper {

  @OnMethodExit
  public static void initField(@FieldValue(value = "isNew", readOnly = false) boolean value) {
    value = !value;
  }
}

并尝试按如下方式分配:

builder.constructor(ElementMatchers.any())
  .intercept(Advice.to(Helper.class));

我本来希望在原始声明的末尾添加建议,但在构建过程中我收到以下错误:

Failed to transform class files in …: Cannot call super (or default) method for public ….SomeSample()

替代方法

我认为我还可以保留默认值 (false) 并取反从生成的 isNew() 方法返回的值,而不是翻转字段的值。如果我将助手更改为:

public class Helper {

  public static boolean isNew(@FieldValue(value = "isNew") boolean value) {
    return !value;
  }
}

当我将 isNew() 的代码生成方法更改为以下内容时:

builder = builder.defineMethod("isNew", boolean.class, Visibility.PUBLIC)
  .intercept(MethodDelegation.to(Helper.class));

我得到:

None of [public static boolean ….Helper.isNew(boolean)] allows for delegation from public boolean SomeSample.isNew()

有什么想法吗?

这可能是一个不幸的 API 选择,但您可以将 Advice 用作装饰器和拦截器。您可能想要设置的是:

builder = builder.visit(Advice.to(Helper.class).on(isConstructor()))

这将围绕现有代码添加代码。使用建议的方法,您可以围绕对原始实现的调用替换方法。如果您定义了一个新方法,则这样的实现不存在并且会产生您看到的错误。

builder = builder
  .defineField("isNew", boolean.class, Visibility.PRIVATE)
  .annotateField(yourTransientAnnotationLiteral);

builder = builder
  .defineConstructor(Visibility.PUBLIC)
  .intercept(MethodCall.invokeSuper()
               .andThen(FieldAccessor.of("isNew")
                          .setsValue(Boolean.TRUE)));

builder = builder
  .defineMethod("isNew", boolean.class, Visibility.PUBLIC)
  .intercept(FieldAccessor.of("isNew"));

builder = builder
  .defineMethod("markNotNew", TypeDescription.VOID, Visibility.PACKAGE_PRIVATE)
  .intercept(FieldAccessor.of("isNew")
               .setsValue(Boolean.FALSE));

这是未经测试的(我特别不确定 isNew() 方法的实现)但我希望能让您了解一种不使用 @Advice 或以编程方式执行此操作的通用方法其他更高级别的机制。