我们如何在编译时访问 ByteBuddy 生成的方法?

How can we access methods generated by ByteBuddy in compilation time?

我写了这个例子:

E someCreateMethod(Class<E> clazz) {
    Class<? extends E> dynamicType = new ByteBuddy()
            .subclass(clazz)
            .name("NewEntity")
            .method(named("getNumber"))
            .intercept(FixedValue.value(100))
            .defineField("stringVal", String.class, Visibility.PRIVATE)
            .defineMethod("getStringVal", String.class, Visibility.PUBLIC)
            .intercept(FieldAccessor.ofBeanProperty())
            .make()
            .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
            .getLoaded();

    return dynamicType.newInstance();
}

我想用它来获得重新定义的 number 属性:

Integer num = someCreateMethod(EntityExample.class).getNumber();  //(1)

或者获取新定义的stringVal属性:

String sVal = someCreateMethod(EntityExample.class).getStringVal(); //(2)  

我的问题是 (1) 工作得很好,而 (2) 不行。我收到以下错误:

Error:(40, 67) java: cannot find symbol

symbol:   method getStringVal()

此外,是否可以使用动态生成的 class:

NewEntity newEntity = someCreateMethod(EntityExample.class);
Integer num = newEntity.getNumber();
String sVal = newEntity.getStringVal();

?

编辑:感谢您的帮助,这个例子是我第一次尝试使用 ByteBuddy 库。我想 defineMethod 实际上定义了一个接口方法的实现,而不是只是在 class 中添加了一个随机方法。所以我决定在这里解释一下我到底想完成什么。

对于 class E 中的每个 Date 属性,我想再添加两个字段(以及它们各自的 getter 和 setter),比方说 (atribute name)InitialDate(atribute name)FinalDate, 这样我就可以为 E.

中的每个日期使用间隔函数

我想知道是否可以使用代码生成来添加这些方法,而不必为每个 E.

创建子classes

PS: E无法更改,它属于遗留模块。

PS2:我不知道每个实体中会有多少个日期属性E,但是新的属性和方法将使用约定创建(例如__FisrtDay , __LastDay), 如下图:

NewA a = eb.create(A.class);
a.getDeadLine(); //inherited
a.getDeadLineFirstDay(); //added 
a.getDeadLineLastDay(); //added

NewA b = eb.create(B.class);
b.getBirthday(); //inherited
b.getBirthdayFirstDay(); //added
b.getBirthdayLastDay(); //added

b.getAnniversary(); //inherited
b.getAnniversaryFirstDay(); //added
b.getAnniversaryLastDay(); //added

PS3:我正在努力实现的目标是通过 ByteBuddy 实现的,还是根本无法实现?还有别的办法吗?

PS4:我的编辑应该是一个新问题吗?

您需要 E 是包含您尝试调用的方法的超级class/ 或接口——您将无法解析 E 上不存在的子类型方法。

这不是 ByteBuddy 的问题,这是您的 class 设计的问题 -- 您应该将要生成的功能设计并分组为可抽象的部分,以便可以通过以下类型公开在编译时有意义。

例如,我们可以使用超类型 'ValueProvider',然后使用 ByteBuddy 定义 IntConstantProvider。

public interface ValueProvider<T> {
    public T getValue();
}

Class<? extends ValueProvider<Integer>> dynamicType = new ByteBuddy()
    .subclass(clazz)
    .name("ConstantIntProvider")
    .method(named("getValue"))
    .intercept(FixedValue.value(100))
    // etc.

你的原型有 3 个独立的功能(如果我们认为未引用私有字段是某些预期行为的存根),没有明显的抽象来包含它们。这可以更好地设计为 3 个简单的原子行为,对于它们的抽象是显而易见的。

您可以使用反射在任意 dynamically-defined class 上找到任意方法,但这从编码或设计 POV 来看并没有真正意义(您的代码如何知道要调用哪些方法?如果它确实知道,为什么不使用类型来表达它呢?)也不是很高效。

对问题的后续编辑 -- Java Bean 属性通过反射工作,因此查找 "related properties" 的示例(例如第一个/最后一个日期)从已知的属性来看并非没有道理。

然而,可以考虑使用 DateInterval( FirstDate, LastDate) class 这样每个基础只需要 一个 补充 属性 属性。

正如 Thomas 指出的那样,Byte Buddy 在运行时生成 类,因此您的编译器无法在编译期间验证它们的存在。

您可以做的是在构建时应用您的代码生成。如果您的 EntityExample.class 存在于特定模块中,您可以使用 Byte Buddy Maven 或 Gradle 插件增强此模块,然后,在增强之后,让您的编译器验证它们的存在。

您还可以定义接口,例如

interface StringVal {
  String getStringVal();
}

你可以要求 Byte Buddy 在你的子类中实现它,如果你将你的子类表示为这个接口,它允许你的编译器验证方法的存在。

除此之外,您的编译器正在做它应该做的事情:告诉您您正在调用一个不存在的方法(当时)。