2

我使用 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 中的错误引起的,还是在这种情况下我实际上应该使用“方法”参数?

4

2 回答 2

1

使用Method参数并启用缓存。如果您首先遇到任何性能问题,那应该可以解决您的大部分性能问题。

请参阅javadoc@Origin

public abstract boolean cacheMethod

如果此值设置为 true 并且带注释的参数是 Method 类型,则分配给此参数的值将缓存在静态字段中。否则,在每次调用被拦截的方法时,都会从其定义类中查找该实例。

方法查找通常由其定义的 Class 缓存,这使得重复查找方法的成本很小。但是,由于 Method 实例可以通过其 AccessibleObject 联系人进行更改,因此任何查找到的实例都需要在公开之前由其定义的 Class 复制。例如,当一个方法在循环中被重复调用时,这可能会导致性能不足。通过启用方法缓存,可以通过将任何拦截方法的单个 Method 实例缓存为检测类型中的静态字段来避免这种性能损失。

于 2015-01-22T22:51:33.227 回答
1

请参阅 Jeor 的答案,这是完全正确的(您应该将其标记为已接受)。只有两个不适合评论的评论:

你当然应该只使用 aMethodHandle而不是 aMethod如果前者允许你做什么。调用MethodHandles 意味着一些 JVM 魔法。句柄由 JVM 使用多态签名解析,即它们的参数不能被装箱,因为 JVM 将简单地用方法调用替换调用站点。因此,在您的情况下,这不起作用。然而,方法句柄的优点是它可以存储在类的常量池中。它是一个原生概念,可以通过字节码指令访问。与此相比,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();
于 2015-01-23T07:30:04.877 回答