Browse Source

Support varargs-only MethodHandle as SpEL function

Prior to this commit, if a MethodHandle was registered as a custom
function in the Spring Expression Language (SpEL) for a static method
that accepted only a variable argument list (for example,
`static String func(String... args)`), attempting to invoke the
registered function within a SpEL expression resulted in a
ClassCastException because the varargs array was unnecessarily wrapped
in an Object[].

This commit modifies the logic in FunctionReference's internal
executeFunctionViaMethodHandle() method to address that.

Closes gh-34109
pull/34398/head
Sam Brannen 1 year ago
parent
commit
c1236a3340
  1. 5
      spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java
  2. 7
      spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java
  3. 23
      spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java

5
spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java

@ -229,8 +229,9 @@ public class FunctionReference extends SpelNodeImpl { @@ -229,8 +229,9 @@ public class FunctionReference extends SpelNodeImpl {
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
if (isSuspectedVarargs) {
if (declaredParamCount == 1) {
// We only repackage the varargs if it is the ONLY argument -- for example,
if (declaredParamCount == 1 && !methodHandle.isVarargsCollector()) {
// We only repackage the arguments if the MethodHandle accepts a single
// argument AND the MethodHandle is not a "varargs collector" -- for example,
// when we are dealing with a bound MethodHandle.
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
methodHandle.type().parameterArray(), functionArgs);

7
spring-expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java

@ -108,11 +108,16 @@ class TestScenarioCreator { @@ -108,11 +108,16 @@ class TestScenarioCreator {
"formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class));
testContext.registerFunction("formatObjectVarargs", formatObjectVarargs);
// #formatObjectVarargs(format, args...)
// #formatPrimitiveVarargs(format, args...)
MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class));
testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs);
// #varargsFunctionHandle(args...)
MethodHandle varargsFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"varargsFunction", MethodType.methodType(String.class, String[].class));
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);
// #add(int, int)
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"add", MethodType.methodType(int.class, int.class, int.class));

23
spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java

@ -79,6 +79,8 @@ class VariableAndFunctionTests extends AbstractExpressionTests { @@ -79,6 +79,8 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
@Test
void functionWithVarargs() {
// static String varargsFunction(String... strings) -> Arrays.toString(strings)
evaluate("#varargsFunction()", "[]", String.class);
evaluate("#varargsFunction(new String[0])", "[]", String.class);
evaluate("#varargsFunction('a')", "[a]", String.class);
@ -241,6 +243,27 @@ class VariableAndFunctionTests extends AbstractExpressionTests { @@ -241,6 +243,27 @@ class VariableAndFunctionTests extends AbstractExpressionTests {
evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
}
@Test // gh-34109
void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() {
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)
evaluate("#varargsFunctionHandle()", "[]", String.class);
evaluate("#varargsFunctionHandle(new String[0])", "[]", String.class);
evaluate("#varargsFunctionHandle('a')", "[a]", String.class);
evaluate("#varargsFunctionHandle('a','b','c')", "[a, b, c]", String.class);
evaluate("#varargsFunctionHandle(new String[]{'a','b','c'})", "[a, b, c]", String.class);
// Conversion from int to String
evaluate("#varargsFunctionHandle(25)", "[25]", String.class);
evaluate("#varargsFunctionHandle('b',25)", "[b, 25]", String.class);
evaluate("#varargsFunctionHandle(new int[]{1, 2, 3})", "[1, 2, 3]", String.class);
// Strings that contain a comma
evaluate("#varargsFunctionHandle('a,b')", "[a,b]", String.class);
evaluate("#varargsFunctionHandle('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
// null values
evaluate("#varargsFunctionHandle(null)", "[null]", String.class);
evaluate("#varargsFunctionHandle('a',null,'b')", "[a, null, b]", String.class);
}
@Test
void functionMethodMustBeStatic() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();

Loading…
Cancel
Save