我使用Byte Buddy(v0.5.2)动态创建接口的“子类”(实际上,我想创建一个实现该接口的类)。在此类的实例上调用的所有方法都应重定向到另一个(拦截器)类。 我使用了以下代码(“TestInterface”是一个接口,它只声明了一个方法“sayHello”):
final Interceptor interceptor = new Interceptor();
Class<?> clazz = new ByteBuddy()
.subclass(TestInterface.class)
.method(any()).intercept(MethodDelegation.to(interceptor))
.make()
.load(TestInterface.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
TestInterface instance = (TestInterface) clazz.newInstance();
instance.sayHello();
拦截器类看起来像这样:
public class Interceptor {
public Object intercept(@Origin MethodHandle method, @AllArguments Object[] args) throws Throwable {
...
}
}
但是,当我尝试调用“sayHello”方法(我的代码示例的最后一行)时,我得到一个“IncompatibleClassChangeError”。堆栈跟踪如下:
Exception in thread "main" java.lang.IllegalAccessError: no such method: byteuddytest.TestInterface.sayHello()void/invokeVirtual
at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:448)
at bytebuddytest.TestInterface$ByteBuddy$0E9xusGs.sayHello(Unknown Source)
at bytebuddytest.Main.main(Main.java:32)
Caused by: java.lang.IncompatibleClassChangeError: Found interface bytebuddytest.TestInterface, but class was expected
at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:965)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:990)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1387)
at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(MethodHandles.java:1732)
at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:442)
... 2 more
问题似乎与我的拦截器方法中使用“MethodHandle”参数有关。当我将类型更改为“方法”时,一切正常。但根据文档,由于性能原因,“MethodHandle”应优先于“Method”。
错误是由Byte Buddy中的错误引起的,还是我应该在这种情况下使用“Method”参数?
答案 0 :(得分:1)
使用Method
参数并启用缓存。如果您有任何问题,那么这应解决您的大多数性能问题。
@Origin
见javadoc:
public abstract boolean cacheMethod
如果此值设置为true且带注释的参数是Method类型,则分配给此参数的值将缓存在静态字段中。否则,在每次调用截获的方法时,都会从其定义的Class中查找实例。
方法查找通常由其定义的类进行缓存,这使得重复查找方法变得非常昂贵。但是,由于Method实例可由其AccessibleObject联系人进行更改,因此任何查找的实例都需要在其公开之前由其定义的类复制。当例如在循环中重复调用方法时,这可能导致性能不足。通过启用方法缓存,可以通过将任何截获的方法的单个Method实例缓存为检测类型中的静态字段来避免此性能损失。
答案 1 :(得分:1)
看到Jeor的答案完全正确(你应该将其标记为已接受)。只有两个评论不适合评论:
如果前者允许您执行操作,您当然应该只使用MethodHandle
而不是Method
。调用MethodHandle
s意味着一些JVM魔法。句柄由JVM通过多态签名解析,即它们的参数不能被装箱,因为JVM将简单地用方法调用替换调用站点。在您的情况下,这不起作用。然而,方法句柄的优点是它可以存储在类的常量池中。它是 native 概念,可以通过字节代码指令访问。与此相比,需要明确生成Method
引用。
因此,您应该缓存Method
实例(这是可变的!)。另请注意,您目前还在拦截Object
的方法。您可以通过以下方式清理代码:
Class<? extends TestInterface> clazz = new ByteBuddy()
.subclass(TestInterface.class)
.method(isDeclaredBy(TestInterface.class))
.intercept(MethodDelegation.to(interceptor))
.make()
.load(TestInterface.class.getClassLoader(),
ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
TestInterface instance = clazz.newInstance();