英文:
Creating a proxy with ByteBuddy that should call a protected method, I get: java.lang.VerifyError: Bad access to protected data in invokevirtual
问题
I'm sorry, but it seems like you've requested not to translate code. If you have any specific questions or need assistance with a particular aspect of the code, please feel free to ask, and I'll do my best to help.
英文:
I'm trying to create a proxy with ByteBuddy that can delegate calls of a protected method getRawId of a MyEntityA class to the same method of an object of the same class referenced in a target field.
package it.mict.lab.bytebuddy.entity;
public class MyEntityA {
private int id;
public MyEntityA() {
}
public MyEntityA(int id) {
this.id = id;
}
protected int getRawId() {
return id;
}
}
The proxy should do something similar to this MyEntityB class:
package it.mict.lab.bytebuddy.entity;
public class MyEntityB extends MyEntityA {
private MyEntityA _target;
public MyEntityB(MyEntityA _target) {
this._target = _target;
}
public void hello() {
System.out.println(_target.getRawId());
}
}
And this is an example of what I would achieve:
package it.mict.lab.bytebuddy;
import java.lang.reflect.Constructor;
import it.mict.lab.bytebuddy.entity.MyEntityA;
import it.mict.lab.bytebuddy.entity.MyEntityB;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType.Unloaded;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType.NamingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
public class App
{
private static Class<? extends MyEntityA> creteProxyClass() {
Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
.with(new NamingStrategy.SuffixingRandom("MyProxy"))
.subclass(MyEntityA.class)
.defineField("_target", MyEntityA.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameter(MyEntityA.class)
.intercept(
MethodCall.invoke(getDefaultConstructor(MyEntityA.class))
.andThen(FieldAccessor.ofField("_target").setsArgumentAt(0))
)
.method(ElementMatchers.nameStartsWith("getRaw")
.or(ElementMatchers.nameStartsWith("setRaw")))
.intercept(MethodDelegation.toField("_target"))
.make();
Class<? extends MyEntityA> proxyClass = unloadedClass
.load(MyEntityA.class.getClassLoader())
.getLoaded();
return proxyClass;
}
private static Constructor<?> getDefaultConstructor(Class<MyEntityA> entityClass) {
for (Constructor<?> constructor : entityClass.getDeclaredConstructors()) {
if (constructor.getParameterCount() == 0) {
return constructor;
}
}
throw new IllegalStateException();
}
public static void main( String[] args ) throws Exception
{
MyEntityB entityB = new MyEntityB(new MyEntityA(123));
entityB.hello();
Class<? extends MyEntityA> proxyClass = creteProxyClass();
System.out.println("MyEntityA package : " + MyEntityA.class.getPackage().getName());
System.out.println("Proxy package : " + proxyClass.getPackage().getName());
Constructor<? extends MyEntityA> proxyConstructor = proxyClass.getConstructor(new Class<?>[] { MyEntityA.class });
MyEntityA proxy = proxyConstructor.newInstance(new MyEntityA());
}
}
I'm using:
OpenJDK Runtime Environment (Temurin)(build 1.8.0_332-b09)
OpenJDK 64-Bit Server VM (Temurin)(build 25.332-b09, mixed mode)
and ByteBuddy version 1.14.4
When I execute this App class, I expect no errors, while I get:
123
MyEntityA package : it.mict.lab.bytebuddy.entity
Proxy package : it.mict.lab.bytebuddy.entity
Exception in thread "main" java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
Location:
it/mict/lab/bytebuddy/entity/MyEntityA$ByteBuddy$GZzebPWq.getRawId()I @4: invokevirtual
Reason:
Type 'it/mict/lab/bytebuddy/entity/MyEntityA' (current frame, stack[0]) is not assignable to 'it/mict/lab/bytebuddy/entity/MyEntityA$ByteBuddy$GZzebPWq'
Current Frame:
bci: @4
flags: { }
locals: { 'it/mict/lab/bytebuddy/entity/MyEntityA$ByteBuddy$GZzebPWq' }
stack: { 'it/mict/lab/bytebuddy/entity/MyEntityA' }
Bytecode:
0x0000000: 2ab4 000a b600 0cac
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
at java.lang.Class.getConstructor(Class.java:1825)
at it.mict.lab.bytebuddy.App.main(App.java:63)
123 is what is printed using the MyEntityB class, then the next two lines check that that MyEntityA class and the one created by ByteBuddy are in the same package, and then there is the exception encountered while creating the instance of the proxy.
If I change MyEntityA.getRawId() visibility from protected to public, everything works fine (but of course, I need it to be protected).
答案1
得分: 1
根据此(旧)问题,这是一个bytebuddy的错误,那里解释的解决方法是为子类使用相同的包,所以:
Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
.with(new NamingStrategy.SuffixingRandom("MyProxy"))
变成
Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
.with(new NamingStrategy.SuffixingRandom("it.mict.lab.bytebuddy.entity.MyProxy"))
还请确保使用与MyEntityA相同的类加载器加载类。
英文:
According to this (old) issue it's a bytebuddy bug, the workaround explained there is to use the same package for the Subclass, So:
Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
.with(new NamingStrategy.SuffixingRandom("MyProxy"))
becomes
Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
.with(new NamingStrategy.SuffixingRandom("it.mict.lab.bytebuddy.entity.MyProxy"))
Also make sure to load the class with the same Classloader as MyEntityA.
答案2
得分: 0
以下是您提供的代码的翻译部分:
"Being sure the classloader is the same for both the original class and the proxy is not easy at is seems. In fact I found a way to force the same classloader in Java 8, but it did not work in Java 17.
Then I found a wonderful 2018 article from the author of ByteBuddy JDK 11 and proxies in a world past sun.misc.Unsafe where he shows the correct way to specify class loading strategy and now my example works for both Java 8 and Java 17.
I copy here the new version of the App class example that shows that solution:"
请注意,这只是您提供的代码的翻译部分,不包括代码本身。如果您需要有关代码的任何解释或其他问题的帮助,请随时提问。
英文:
Being sure the classloader is the same for both the original class and the proxy is not easy at is seems. In fact I found a way to force the same classloader in Java 8, but it did not work in Java 17.
Then I found a wonderful 2018 article from the author of ByteBuddy JDK 11 and proxies in a world past sun.misc.Unsafe where he shows the correct way to specify class loading strategy and now my example works for both Java 8 and Java 17.
I copy here the new version of the App class example that shows that solution:
package it.mict.lab.bytebuddy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import it.mict.lab.bytebuddy.entity.MyEntityA;
import it.mict.lab.bytebuddy.entity.MyEntityB;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType.Unloaded;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType.NamingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
public class App
{
private static Class<? extends MyEntityA> creteProxyClass() throws Exception {
Unloaded<MyEntityA> unloadedClass = new ByteBuddy()
.with(new NamingStrategy.SuffixingRandom("MyProxy"))
.subclass(MyEntityA.class)
.defineField("_target", MyEntityA.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameter(MyEntityA.class)
.intercept(
MethodCall.invoke(getDefaultConstructor(MyEntityA.class))
.andThen(FieldAccessor.ofField("_target").setsArgumentAt(0))
)
.method(ElementMatchers.nameStartsWith("getRaw")
.or(ElementMatchers.nameStartsWith("setRaw")))
.intercept(MethodDelegation.toField("_target"))
.make();
// This is the strategy part shown in the Rafael blog
ClassLoadingStrategy<ClassLoader> strategy;
if (ClassInjector.UsingLookup.isAvailable()) {
Class<?> methodHandles = Class
.forName("java.lang.invoke.MethodHandles");
Object lookup = methodHandles.getMethod("lookup").invoke(null);
Method privateLookupIn = methodHandles.getMethod("privateLookupIn",
Class.class,
Class.forName("java.lang.invoke.MethodHandles$Lookup"));
Object privateLookup = privateLookupIn.invoke(null, MyEntityA.class,
lookup);
strategy = ClassLoadingStrategy.UsingLookup.of(privateLookup);
} else if (ClassInjector.UsingReflection.isAvailable()) {
strategy = ClassLoadingStrategy.Default.INJECTION;
} else {
throw new IllegalStateException(
"No code generation strategy available");
}
Class<? extends MyEntityA> proxyClass = unloadedClass
.load(MyEntityA.class.getClassLoader(), strategy)
.getLoaded();
return proxyClass;
}
private static Constructor<?> getDefaultConstructor(Class<MyEntityA> entityClass) {
for (Constructor<?> constructor : entityClass.getDeclaredConstructors()) {
if (constructor.getParameterCount() == 0) {
return constructor;
}
}
throw new IllegalStateException();
}
public static void main( String[] args ) throws Exception
{
System.out.println("Java version: " + System.getProperty("java.version"));
MyEntityB entityB = new MyEntityB(new MyEntityA(123));
entityB.hello();
Class<? extends MyEntityA> proxyClass = creteProxyClass();
System.out.println("MyEntityA package : " + MyEntityA.class.getPackage().getName());
System.out.println("Proxy package : " + proxyClass.getPackage().getName());
System.out.println("MyEntityA ClassLoader : " + MyEntityA.class.getClassLoader());
System.out.println("Proxy ClassLoader : " + proxyClass.getClassLoader());
Constructor<? extends MyEntityA> proxyConstructor = proxyClass.getConstructor(new Class<?>[] { MyEntityA.class });
MyEntityA proxy = proxyConstructor.newInstance(new MyEntityA());
System.out.println("Proxy: " + proxy);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论