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).