当 toString() 未定义时,ElementMatchers::isToString 在 ByteBuddy 1.11.1 中失败;我使用的匹配模式是否正确?

问题描述

场景:我正在创建一个代理类 (com.foo.bar.Proxy$Z),它将它代理的类 (Z.class) 子类化并包装它的一个实例。 (非常标准的东西。)

我有一个 Matcher 可以弄清楚什么是“商业方法”。它做什么并不重要,但让我们说这个问题它不匹配toString

我希望按顺序应用以下规则:

  • 首先,让我的代理类的 toString 方法将任何 toString 调用简单地转发到实例。
  • 其次,如果在我的代理类的实例上调用业务方法,它将被转发到该实例。 (这与上面的规则相同;第一条规则非常具体,确保即使业务方法匹配排除 toString,它也会被“选中”。)

这些是非常标准的规则。

这里是一些 ByteBuddy“匹配”代码,希望能实现这一点,我在几个版本的 ByteBuddy 中都没有改变。我升级到 1.11.1 版本,现在突然失败了,这表明推荐的匹配模式不再有效,或者我以某种方式误解了它们并且过去很幸运。

这是代码的简化快照:

/*...*/
// my own matcher; irrelevant here except that it comes "second"
.method(isBusinessMethod())

// straightforward ByteBuddy proxying pattern
.intercept(MethodCall.invokeSelf()
           .onMethodCall(MethodCall.invoke(named("proxied")))
           .withAllArguments())

// the problematic bit; this used to work
.method(isToString()) // from ElementMatchers

// same implementation strategy as above
.intercept(MethodCall.invoke(named("toString"))
           .onMethodCall(MethodCall.invoke(named("proxied"))));

这曾经是成功的。现在我得到:

java.lang.IllegalStateException: class com.foo.bar.Proxy$Z does not define exactly one virtual method or constructor for name(equals(toString)) but contained 0 candidates: []
        at net.bytebuddy.implementation.MethodCall$MethodLocator$ForElementMatcher.resolve(MethodCall.java:816)

生成的代理类 (com.foo.bar.Proxy$Z.class) 本身并没有定义 toString() 方法(这就是我在这里使用匹配器的原因)。但是它扩展的 Z.class 确实定义了一个 toString() 方法

如果调用获取代理类的实例,并对其调用 toString(),我的期望是我的 Z 代理实例容纳并从它的 ( com.foo.bar.Proxy$Z) private 方法会将调用转发给它。

ByteBuddy tutorial 说:

Byte Buddy 以堆栈形式组织覆盖方法的规则。这意味着,每当您注册用于覆盖方法的新规则时,ít 都会被压入该堆栈的顶部,并且始终首先应用,直到添加新规则,然后该规则将具有更高的优先级。

它还说:

因为这个组织,你应该总是最后注册更具体的方法匹配器。否则,之后注册的任何不太具体的方法匹配器可能会阻止应用您之前定义的规则。

这正是我在上面尝试做的并且在之前的 ByteBuddy 版本中有效。具体:

  • proxied() 首先被尝试。如果匹配,则将采用其对应的 isToString() 逻辑。我的期望是它总是匹配(总是有一个“可达”的 intercept 方法,无论你的代理类从什么扩展而来)。
  • toString() 第二次尝试。如果匹配,则将采用其对应的 isBusinessMethod() 逻辑。

我误解了这是如何工作的吗?或者 ByteBuddy 1.11.1 是否更改了 1.11.0 及更早版本的逻辑?如果我误解了,这在过去是如何工作的?

Rafael 提到他“更改了匹配器考虑的方法以排除检测方法本身”。在这种情况下,我认为 intercept 是检测方法?也许这太激进了,正如他所建议的:请记住,我的代理类 (Z::toString) 的超类型 (Z.class) 定义了一个 com.foo.bar.Proxy$Z 方法。或许这个改动没有考虑到这种情况?

解决方法

我更改了匹配器考虑的方法以排除检测方法本身。我没有多想,我认为这永远不会是匹配的理想结果。请考虑一下changed code,也许我的改变太短视了?