在抽象基础 class 上委派给 setter 导致加载时出现 VerifyError

Delegation to setter on abstract base class results in VerifyError on load

这个问题与我之前在这里的问题有关:How to create a default constructor with Byte Buddy

我正在创建一个子class,它在委托方法调用到某个实例之前首先设置一些上下文。这已经很好地解决了一个问题。

加载动态创建的子程序时出现以下错误class。

    java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
  Location:
    com/frequentis/ps/service/test/saga/ProxyTestSaga$ByteBuddy$Rm8DV3Lj.setTimeoutManager(Lcom/codebullets/sagalib/timeout/TimeoutManager;)V @3: invokevirtual
  Reason:
    Type 'com/frequentis/ps/service/test/saga/ProxyTestSaga' (current frame, stack[0]) is not assignable to 'com/frequentis/ps/service/test/saga/ProxyTestSaga$ByteBuddy$Rm8DV3Lj'
  Current Frame:
    bci: @3
    flags: { }
    locals: { 'com/frequentis/ps/service/test/saga/ProxyTestSaga$ByteBuddy$Rm8DV3Lj', 'com/codebullets/sagalib/timeout/TimeoutManager' }
    stack: { 'com/frequentis/ps/service/test/saga/ProxyTestSaga' }
  Bytecode:
    0x0000000: b200 0cb6 0010 57b1                    

    at java.lang.Class.getDeclaredFields0(Native Method)
    at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
    at java.lang.Class.getDeclaredField(Class.java:2068)
    at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:124)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:200)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:200)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.initialize(DynamicType.java:3497)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:3485)
    at com.frequentis.ps.service.test.saga.DynamicSagaTypeBuilder.buildAndLoad(DynamicSagaTypeBuilder.java:65)
    at com.frequentis.ps.service.test.saga.MoreAbstractSpaceSagaUnitTest.generateProxyClassForSagaUnderTest(MoreAbstractSpaceSagaUnitTest.java:274)
    at com.frequentis.ps.service.test.saga.AbstractSpaceSagaUnitTest.enhance(AbstractSpaceSagaUnitTest.java:105)
    at com.frequentis.ps.service.test.saga.ProxyTestSagaTest.before(ProxyTestSagaTest.java:27)

目前这是我的字节伙伴设置,它适用于几乎所有情况,除了导致显示错误的“setTimeoutManager”和“setState”方法。

// called within the unit test base class (as shown in the call stack above)
builder = new ByteBuddy()
    .subclass(sagaUnderTestClass, ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC);
    // define default ctor if necessary that passes "null" values to the super ctor
    builder.method(isAnnotatedWith(StartsSaga.class).or(isAnnotatedWith(EventHandler.class)))
           .intercept(MethodDelegation.to(new ForwardingContextSetupInterceptor<(sagaUnderTest, contextSetter))
                                      .appendParameterBinder(Pipe.Binder.install(Forwarder.class)))
           .method(isPublic()
                           .and(isDeclaredBy(sagaUnderTest.getClass()).or(isDeclaredBy(AbstractSaga.class)
                           .and(not(isAnnotatedWith(StartsSaga.class))).and(not(isAnnotatedWith(EventHandler.class))))
           .intercept(MethodDelegation.to(sagaUnderTest))))

二传手需要不同的设置吗?
还是抽象造成的class?
不太明白为什么说bad access to protected data,是私有域的意思吗?

我的类型层次结构如下所示。声明设置器的最顶层 class:

public abstract class AbstractSaga<SAGA_STATE extends SagaState> implements Saga<SAGA_STATE>, NeedTimeouts, NeedContext {
    private SAGA_STATE state;
    private boolean completed;
    private TimeoutManager timeoutManager;
    private ExecutionContext context;

    protected AbstractSaga() {
        completed = false;
    }

    // i have omitted some method for clarity

    protected ExecutionContext context() {
        return context;
    }

    @Override
    public SAGA_STATE state() {
        return state;
    }

    @Override
    public void setState(final SAGA_STATE state) {
        this.state = state;
    }

    @Override
    public boolean isFinished() {
        return completed;
    }

    protected void setFinished() {
        completed = true;
    }

    @Override
    public void setTimeoutManager(final TimeoutManager timeoutManager) {
        this.timeoutManager = timeoutManager;
    }
}

扩展:

public abstract class AbstractSpaceSaga<SAGA_STATE extends SpaceSagaState, MESSAGE extends Message> 
    extends AbstractSaga<SAGA_STATE> {
}

最后再次扩展:

public class ProxyTestSaga 
    extends AbstractSpaceSaga<SpaceSagaState, TestRequest> {

    @StartsSaga
    public void handle(final TestRequest request) {
    }

    @EventHandler
    public void handle(final TestEvent event) {
    }
}

我希望代码在某种程度上是可以理解的,如果需要,我可以随时添加更多信息。

这绝对是 Byte Buddy 中的一个错误,因为它不允许您创建非法字节代码。然而,我对正在发生的事情感到困惑,因为 Byte Buddy 似乎正在选择一种与拦截器不兼容的方法。我只是查看了实现,这不应该发生。您使用的是最新版本 (0.6.14) 吗?

从字节码来看,拦截器产生以下代码:

GETSTATIC (interceptor of type ProxyTestSaga)
INVOKEVIRTUAL (some method without arguments)
POP (drop value of the invoked method)
RETURN

调用委托方法时出现错误。调用为代理类型定义的方法。通过查看 Byte Buddy 的代码,使用 MethodDelegation 时这是不可能的,所以我假设另一个拦截器在某处被拾取?

真正能帮助我的是 运行 重现错误的可行示例。也许你可以分解你的代码,这样我就可以 运行 它了。或者,如果您的应用程序是开源的,也许您可​​以向它提供一个 link,这样我就可以 运行 您的代码。或者,与我联系,以便我可以私下访问您的代码。

最后,您可能想查看 Forwarding 工具而不是使用 MethodDelegation 进行第二次拦截。 Beyond,如果你能帮我修复这个bug,我将不胜感激。

嗯,您提供的示例有很多问题,这肯定会使事情变得糟糕。

我觉得您可能应该更好地研究抽象 classes,如何使用它们以及为什么使用它们。

摘要 class 可以用作子class 启动的蓝图。它也可以用作 classes 的中央库,以获取通用字段和方法。摘要 classes 只能包含原始 m 方法。摘要从不初始化。如果你需要它来初始化你必须 subclass 它,并且 subclass 不能初始化,除非它在父抽象 class 中实现所有的 aqbstract 方法。据我所知,您没有在任何一个扩展中初始化您的方法,因此 class 中的 none 每个都变成了一个对象。您可以将 getter 和 setter 放在那里,但它们无法获取或设置任何东西,直到 class 的某些扩展最终完成打开所有内容并成为对象的完整实例。在你做某事之前,它有点像一个内置的图书馆。我用它来保存一个包可能会用到的许多常见的东西,这样我就可以一遍又一遍地抓住它们,并在包的其他地方有更简单的 classes。

总而言之,你的 class ProxyTestSaga 没有初始化,而且必须是抽象的,因为它扩展了抽象 class AbstractSpace saga 并且没有初始化它所有的继承方法,你没有在 ProxyTestSage 做完。

我几乎不敢回答这个问题,因为您编辑了一些代码。但是,我相信您的修订注释意味着原始摘要中有更多代码从未被初始化。当它到达摘要的最终扩展时,就会发生错误。

现在长风已经过去了,我看到你在那里声明了覆盖,它们看起来像是要覆盖并为你加载的 classes 提供一些可访问性 classes.

您应该可以通过完成所有初始化来解决您的问题。

但是我看到其他一些事情让我相信你用错了。看来您正试图从其他几个 classes 中保留一组方法,以便稍后从一个点使用。这不是抽象的目的。您使用一个界面来创建许多面向外的方法的中央存储库。

简而言之,您可以使用一个接口来实现多个方法,获取这些方法,然后其他 classes 可以使用您的接口获取并覆盖它们以达到他们的目的。

另一件事让我相信你用错了东西。 Abstract classes只能初始化1个class。接口不是 classes,可以扩展多个 classes。所以这是你的代码的另一件事。我真的不知道为什么你的 IDE 没有对你尖叫。

您应该花几个小时深入阅读接口和抽象及其规则。它们各有特点和优点,如果使用得当,都是很好的工具。