字节好友导致 IncompatibleClassChangeError
Byte Buddy causes IncompatibleClassChangeError
我使用 Byte Buddy (v0.5.2) 动态创建一个接口的 "subclass"(实际上,我想创建一个实现该接口的 class)。在此 class 实例上调用的所有方法都应重定向到另一个(拦截器)class。
我使用了以下代码("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();
拦截器 class 看起来像这样:
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[=12=]E9xusGs.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"参数的使用有关。当我将类型更改为 "Method" 时,一切正常。但是根据文档,由于性能原因,"MethodHandle" 应该优于 "Method"。
错误是由字节好友中的错误引起的,还是我应该在这种情况下实际使用 "Method" 参数?
使用 Method
参数并启用缓存。如果您首先遇到任何性能问题,那应该可以解决您的大部分性能问题。
参见 javadoc @Origin
:
public abstract boolean cacheMethod
If this value is set to true and the annotated parameter is a Method type, the value that is assigned to this parameter is cached in a static field. Otherwise, the instance is looked up from its defining Class on every invocation of the intercepted method.
Method look-ups are normally cached by its defining Class what makes a repeated look-up of a method little expensive. However, because Method instances are mutable by their AccessibleObject contact, any looked-up instance needs to be copied by its defining Class before exposing it. This can cause performance deficits when a method is for example called repeatedly in a loop. By enabling the method cache, this performance penalty can be avoided by caching a single Method instance for any intercepted method as a static field in the instrumented type.
看到 Jeor 的回答是完全正确的(你应该将其标记为已接受)。只有两条评论不适合评论:
你当然应该只使用 MethodHandle
而不是 Method
,如果前者允许你做的话。调用 MethodHandle
s 意味着一些 JVM 魔法。句柄由 JVM 使用多态签名解析,即它们的参数不能被装箱,因为 JVM 将简单地用方法调用替换调用站点。因此,在您的情况下,这是行不通的。然而,方法句柄的优点是它可以存储在 class 的常量池中。它是一个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();
我使用 Byte Buddy (v0.5.2) 动态创建一个接口的 "subclass"(实际上,我想创建一个实现该接口的 class)。在此 class 实例上调用的所有方法都应重定向到另一个(拦截器)class。 我使用了以下代码("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();
拦截器 class 看起来像这样:
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[=12=]E9xusGs.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"参数的使用有关。当我将类型更改为 "Method" 时,一切正常。但是根据文档,由于性能原因,"MethodHandle" 应该优于 "Method"。
错误是由字节好友中的错误引起的,还是我应该在这种情况下实际使用 "Method" 参数?
使用 Method
参数并启用缓存。如果您首先遇到任何性能问题,那应该可以解决您的大部分性能问题。
参见 javadoc @Origin
:
public abstract boolean cacheMethod
If this value is set to true and the annotated parameter is a Method type, the value that is assigned to this parameter is cached in a static field. Otherwise, the instance is looked up from its defining Class on every invocation of the intercepted method.
Method look-ups are normally cached by its defining Class what makes a repeated look-up of a method little expensive. However, because Method instances are mutable by their AccessibleObject contact, any looked-up instance needs to be copied by its defining Class before exposing it. This can cause performance deficits when a method is for example called repeatedly in a loop. By enabling the method cache, this performance penalty can be avoided by caching a single Method instance for any intercepted method as a static field in the instrumented type.
看到 Jeor 的回答是完全正确的(你应该将其标记为已接受)。只有两条评论不适合评论:
你当然应该只使用 MethodHandle
而不是 Method
,如果前者允许你做的话。调用 MethodHandle
s 意味着一些 JVM 魔法。句柄由 JVM 使用多态签名解析,即它们的参数不能被装箱,因为 JVM 将简单地用方法调用替换调用站点。因此,在您的情况下,这是行不通的。然而,方法句柄的优点是它可以存储在 class 的常量池中。它是一个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();