ByteBuddy 在方法参数上 "spread" 对象数组的能力有哪些限制?

What limitations are there to ByteBuddy's ability to "spread" an Object array over a method's parameters?

我知道 MethodCall#withArgumentArrayElements(int) 方法。简而言之,它允许您接受 Object[] 并调用其他方法,从 int 参数标识的 Object[] 开始按顺序提供其参数。通常你也使用动态分配器。

我发现一个奇怪的限制似乎不起作用。我想了解我做错了什么,或者 ByteBuddy 是否存在(罕见)问题。

我使用 ByteBuddy 生成了一个 static 方法,其 Java 代码可能如下所示:

private static final void setFrob(final T target, final Object... parameters) throws Exception {
  target.setFrob((String)parameters[0],
                 (Integer)parameters[1]); // pseudocode; happens via "spreading" mentioned above
}

正如我希望您能看到的那样,模式是在我的检测 class 中,我定义了一个 private static setter 以“真实” setter 命名的方法方法(出于各种原因)。我接收 targetparameters,无论它们是什么,然后使用提供的 parameterstarget 上调用“真实的”setter 方法为“真实”setter 方法的参数提供值的数组。这并不复杂并且工作正常,但有趣的是仅在某些情况下。

方法定义的 ByteBuddy 配方是:

final MethodDescription staticSetterMethod =
  new MethodDescription.Latent(builder.toTypeDescription(),
                               methodToken.getName(),
                               PRIVATE_STATIC_FINAL_SYNTHETIC_VARARGS_METHOD_MODIFIERS,
                               Collections.emptyList(),
                               TypeDescription.Generic.VOID,
                               List.of(new ParameterDescription.Token(targetType,
                                                                      "target",
                                                                      ParameterManifestation.FINAL.getMask()),
                                       new ParameterDescription.Token(OBJECT_ARRAY_TYPE_DESCRIPTION_GENERIC,
                                                                      "parameters",
                                                                      ParameterManifestation.FINAL.getMask())),
                               Collections.singletonList(EXCEPTION_TYPE_GENERIC),
                               Collections.emptyList(),
                               null,
                               null);
builder = builder
  .define(staticSetterMethod)

因为事情在某些情况下有效,所以我已经能够 javap 结果 class 并且方法定义如我所料。我不担心这部分。

接下来,ByteBuddy 的实现方法如下所示(我尽量保持简短和相关):

MethodCall.invoke(new MethodDescription.Latent(targetType.asErasure(), // T's actual type
                                               methodToken)) // setFrob(String, Integer)
                     .onArgument(0) // target (of type T)
                     .withArgumentArrayElements(1) // parameters
                     .withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC));

…所以:“调用 setFrob,由 target 的类型声明,由 methodToken 描述,在第一个参数 (target) 上传播传入的 Object[] 使用动态分配在第二个参数 (parameters) 中找到的数组值。

我的理解是,如果“真正的”setFrob 方法需要一个 String 和一个 Integer 提供给它,那么如果这里的 Object[] 是两个元素很长,如果第一个元素是 String,第二个元素是 Integer,那么 withArgumentArrayElements(1) 调用将确保这些元素“传播”“到”正确的方法参数中.

确实,当被调用的 setFrob 方法接受零个或一个参数时(假设它被定义为仅接受一个 String 参数),这确实可以正常工作。这告诉我至少我的 ByteBuddy 食谱是正确的。

然而,我非常惊讶地注意到 当被调用的 setFrob 方法被更改为接受两个参数时它失败了。 部分堆栈说:

java.lang.IllegalStateException: public void com.foo.bar.TestExplorations$Foo.setFrob(java.lang.String,java.lang.Integer) does not accept 1 arguments
        at net.bytebuddy.implementation.MethodCall$Appender.toStackManipulation(MethodCall.java:3539)
        at net.bytebuddy.implementation.MethodCall$Appender.apply(MethodCall.java:3508)
        at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:708)
        at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:693)
        at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod.apply(TypeWriter.java:600)
        at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation.create(TypeWriter.java:5660)
        at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2166)
        at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:232)
        at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:204)

错误是由于 this conditional:

ParameterList<?> parameters = invokedMethod.getParameters();
if (parameters.size() != argumentLoaders.size()) {
  throw new IllegalStateException(invokedMethod + " does not accept " + argumentLoaders.size() + " arguments");
}

本例中的 argumentLoaders 大小为 1parameters 的大小为 2

我做错了什么?

我了解到您必须使用withArgumentArrayElements if your receiver method has more than one parameter. The one-argument variant的两个或three-argument变体似乎只能用于具有零参数或一个参数。

Maintainer here: 确实是个bug,在循环中重复了increment。这将在 1.10.15 版本中修复。