代码之家  ›  专栏  ›  技术社区  ›  chschroe

字节伙伴导致不兼容ClassChangeError

  •  2
  • chschroe  · 技术社区  · 10 年前

    我使用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”方法(代码示例的最后一行)时,我得到了一个“不兼容ClassChangeError”。堆栈跟踪如下:

    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”参数?

    2 回复  |  直到 9 年前
        1
  •  1
  •   Jeor Mattan    10 年前

    使用 Method 参数并启用缓存。这应该可以解决大部分性能问题,如果你有任何问题的话。

    看见 javadoc 对于 @Origin :

    public abstract boolean cacheMethod
    

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

    方法查找通常由其定义的Class缓存,这使得重复查找方法的开销不大。然而,由于方法实例通过其AccessibleObject接触是可变的,因此任何查找到的实例在公开之前都需要由其定义类复制。例如,在循环中重复调用方法时,这可能会导致性能不足。通过启用方法缓存,可以通过将任何截获方法的单个方法实例缓存为插入指令类型中的静态字段来避免这种性能损失。

        2
  •  1
  •   Rafael Winterhalter    10 年前

    查看Jeor的答案,这是完全正确的(你应该将其标记为已接受)。只有两个不适合评论的备注:

    当然,您应该只使用 MethodHandle 而不是 Method 如果前者允许你做什么 方法句柄 s暗示了一些JVM魔力。句柄由JVM用多态签名解析,即它们的参数不能装箱,因为JVM只会用方法调用替换调用站点。因此,在您的情况下,这不起作用。然而,方法句柄的优点是它可以存储在类的常量池中。它是一个 出生地的 可由字节码指令访问的概念。与此相比 方法 需要显式生成引用。

    因此,您应该缓存 方法 实例(它是可变的!)。此外,请注意,您当前还拦截了 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();