You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 13:30:16