Byte Buddy特定场景下为何需要使用动态赋值?
我构建了如下TypeDescription.Generic实例:
import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType; final TypeDescription.Generic supplierType = parameterizedType(typePool.describe("java.util.function.Supplier").resolve(), List.of(TypeDescription.Generic.Builder.of(mySuperclass.asGenericType()).asWildcardUpperBound())) .build();
(mySuperclass为非泛型类,即普通TypeDescription)
我认为supplierType准确代表java.util.function.Supplier<? extends MySuperclass>。
接着在DynamicType.Builder(使用子类化策略)中,用该supplierType定义私有字段:
builder = builder.defineField("$supplier", supplierType, PRIVATE, SYNTHETIC, FieldManifestation.FINAL);
我认为该字段定义准确代表private final Supplier<? extends MySuperclass> $supplier;。
随后我定义一个方法,返回调用该Supplier的get()的结果:
builder = builder .defineMethod("$get", mySuperclass, PUBLIC, SYNTHETIC, MethodManifestation.FINAL) .intercept(MethodCall.invoke(named("get")) .onField("$supplier") .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)); // <-- 我的疑问
我认为该方法定义准确对应代码:public final MySuperclass $get() { return $supplier.get(); },但对其返回类型、类型转换及动态赋值存在疑问。
若不配置withAssigner,运行时生成类中的$get()方法会报错,提示对应的Supplier无法将T转换为mySuperclass。我原以为因Supplier通配符类型参数的上界是mySuperclass,无需动态赋值,请问我哪里理解错了?
问题根源:泛型擦除与字节码层面的类型检查
Java泛型是编译期语法糖,字节码层面会执行类型擦除:Supplier<? extends MySuperclass>擦除后实际是Supplier,其get()方法的返回类型会被擦除为Object。
你定义的$get()方法返回类型是MySuperclass,所以字节码层面必须把get()返回的Object强制转换为MySuperclass才能匹配方法返回要求。
你觉得不需要转换,是站在Java源码编译期的泛型检查角度:源码里编译器能通过通配符上界推断get()返回的是MySuperclass的子类,自动插入隐式转换。但ByteBuddy是直接生成字节码,它不会像Java编译器那样自动处理泛型擦除后的隐式转换逻辑——默认情况下,ByteBuddy的方法调用拦截器会严格按照字节码的类型匹配生成代码,不会自动插入类型转换指令,这就导致了运行时的类型转换错误。
为什么withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)能解决问题
Assigner.Typing.DYNAMIC会让ByteBuddy自动生成必要的类型转换字节码,相当于帮你完成了Java编译器在编译时会做的隐式强制转换:将get()返回的Object转换为MySuperclass。如果不指定这个参数,ByteBuddy生成的字节码里没有转换指令,JVM执行时就会因为返回类型不匹配(Object vs MySuperclass)抛出错误。
补充说明
你也可以通过显式指定结果转换的方式实现相同效果,比如在MethodCall.invoke(...)后链式调用transform(Transformer.ForMethodResult.of(TypeDescription.Generic.Builder.of(mySuperclass).build())),但withAssigner的方式更简洁,本质都是生成字节码层面的类型转换指令。
内容的提问来源于stack exchange,提问作者Laird Nelson

