【问题标题】:How can we access methods generated by ByteBuddy in compilation time?我们如何在编译时访问 ByteBuddy 生成的方法?
【发布时间】:2017-05-31 20:28:38
【问题描述】:

我写了这个例子:

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()

另外,是否可以使用动态生成的类来做这样的事情:

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

?

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

对于 E 类中的每个 Date 属性,我想再添加两个字段(以及它们各自的 getter 和 setter),比如说 (atribute name)InitialDate(atribute name)FinalDate,这样我就可以为每个日期使用间隔功能在E

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

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:我的编辑应该是一个新问题吗?

【问题讨论】:

  • 我猜getNumber 是在EntityExample 接口中定义的,而getStringVal 没有。
  • 另外,请原谅我提出的愚蠢问题……但你认为你为什么要这样做?
  • @Joe C 好吧,ByteBuddy 让我们定义新方法,对吧? (因为“.defineMethod”部分)所以我认为它应该给我们一些方法来访问我们正在创建的方法......我只是不知道该怎么做。至于您的第二条评论,我需要动态创建我作为参数传递给方法“someCreateMethod”的任何类的子类,并且这些子类应该实现两个新方法。这就是我想要这个的原因。

标签: java code-generation byte-buddy


【解决方案1】:

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

这不是 ByteBuddy 问题,这是你的类设计问题——你应该设计和分组你打算生成的功能到可抽象的部分,这样它就可以通过在编译时有意义的类型来公开。

例如,我们可以使用超类型“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 个简单的原子行为,其中抽象是显而易见的。

您可以使用反射在任意动态定义的类上查找任意方法,但这对于编码或设计 POV 并没有真正意义(您的代码如何知道要调用哪些方法?如果知道,为什么不使用表达这一点的类型?)也不是很高效。

对问题进行编辑 -- Java Bean 属性通过反射工作,因此从已知属性中查找“相关属性”(例如 First/Last Date)的示例并非不合理。

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

【讨论】:

  • 谢谢托马斯!你能看看我对这个问题的编辑吗?我试图更好地解释我正在尝试做的事情。欢迎任何想法/建议/更正。
  • 感谢@ReyaGistrout 阐明您的目的,已编辑我的答案。
【解决方案2】:

正如 Thomas 所指出的,Byte Buddy 在运行时生成类,这样您的编译器就无法在编译时验证它们的存在。

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

您还可以做的是定义像

这样的接口
interface StringVal {
  String getStringVal();
}

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

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

【讨论】:

    猜你喜欢
    • 2020-10-25
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 2018-12-23
    • 2010-09-17
    • 2016-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多