使用ByteBuddy创建代理调用受保护方法时遇VerifyError求助
问题:ByteBuddy生成代理类调用protected方法时抛出VerifyError
环境信息
- OpenJDK Runtime Environment (Temurin)(build 1.8.0_332-b09)
- OpenJDK 64-Bit Server VM (Temurin)(build 25.332-b09, mixed mode)
- ByteBuddy版本:1.14.4
问题描述
尝试用ByteBuddy创建MyEntityA的代理类,将受保护方法getRawId()的调用委托给代理类中_target字段引用的同类型对象,预期逻辑与自定义子类MyEntityB一致。但运行时抛出java.lang.VerifyError,提示Bad access to protected data in invokevirtual;将getRawId改为public则运行正常。
相关代码
MyEntityA.java
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; } }
MyEntityB.java
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()); } }
App.java
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()); } }
报错信息
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)
原因分析
JVM对protected方法的调用有严格的访问控制规则:子类调用父类的protected方法时,被调用的对象必须是子类本身或其子类实例(即类型可赋值给子类)。
ByteBuddy生成的代理类中,MethodDelegation.toField("_target")会直接生成调用_target.getRawId()的字节码,但_target是MyEntityA类型,无法赋值给代理子类,触发JVM的验证错误。
而自定义子类MyEntityB能正常调用,是因为二者处于同一包下,JVM允许同包类直接访问protected成员,绕过了子类实例的类型检查。
解决方案
方案1:用MethodCall替代MethodDelegation
通过MethodCall显式调用目标方法,ByteBuddy会自动生成符合JVM验证规则的字节码:
.method(ElementMatchers.nameStartsWith("getRaw") .or(ElementMatchers.nameStartsWith("setRaw"))) .intercept(MethodCall.invoke(ElementMatchers.named("getRawId")) .onField("_target"))
方案2:利用同包访问权限封装委托逻辑
在代理类中添加辅助方法,通过该方法调用_target的protected方法,借助同包权限绕过类型检查:
.defineMethod("delegateGetRawId", int.class, Visibility.PRIVATE) .intercept(MethodCall.invoke(ElementMatchers.named("getRawId")) .onField("_target")) .method(ElementMatchers.nameStartsWith("getRaw")) .intercept(MethodCall.invoke(ElementMatchers.named("delegateGetRawId")) .onThis())
方案3:临时调整方法可见性(不推荐)
将getRawId改为public,可快速解决问题,但会破坏类的封装性,仅适合测试场景。
内容的提问来源于stack exchange,提问作者Marco Dabbene
相关产品推荐
相关产品推荐

