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 命名的方法方法(出于各种原因)。我接收 target
和 parameters
,无论它们是什么,然后使用提供的 parameters
在 target
上调用“真实的”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
大小为 1
。 parameters
的大小为 2
。
我做错了什么?
我了解到您必须使用withArgumentArrayElements
if your receiver method has more than one parameter. The one-argument variant的两个或three-argument变体似乎只能用于具有零参数或一个参数。
Maintainer here: 确实是个bug,在循环中重复了increment。这将在 1.10.15 版本中修复。
我知道 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 命名的方法方法(出于各种原因)。我接收 target
和 parameters
,无论它们是什么,然后使用提供的 parameters
在 target
上调用“真实的”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
大小为 1
。 parameters
的大小为 2
。
我做错了什么?
我了解到您必须使用withArgumentArrayElements
if your receiver method has more than one parameter. The one-argument variant的两个或three-argument变体似乎只能用于具有零参数或一个参数。
Maintainer here: 确实是个bug,在循环中重复了increment。这将在 1.10.15 版本中修复。