Skip to content

Commit b07217a

Browse files
committed
Use ConversionService to convert POJO to array for SpEL varargs invocations
Prior to this commit, if an appropriate Converter was registered with the ConversionService that converts from a POJO to an array and that ConversionService was registered with the Spring Expression Language (SpEL) TypeConverter, an attempt to invoke a varargs method in a SpEL expression with such a POJO would fail because the ConversionService was not used to convert the POJO to an array suitable for the varargs method invocation. This commit revises the implementations of convertArguments(...) and convertAllMethodHandleArguments(...) in ReflectionHelper to support such use cases. Closes gh-34371
1 parent aa7e84c commit b07217a

File tree

3 files changed

+92
-7
lines changed

3 files changed

+92
-7
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -319,8 +319,8 @@ else if (!sourceType.isAssignableTo(componentTypeDesc) ||
319319
(sourceType.isArray() && !sourceType.isAssignableTo(targetType)) ||
320320
(argument instanceof List)) {
321321

322-
TypeDescriptor targetTypeToUse =
323-
(sourceType.isArray() || argument instanceof List ? targetType : componentTypeDesc);
322+
TypeDescriptor targetTypeToUse = (sourceType.isArray() || argument instanceof List ||
323+
converter.canConvert(sourceType, targetType) ? targetType : componentTypeDesc);
324324
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
325325
}
326326
// Possible outcomes of the above if-else block:
@@ -420,8 +420,8 @@ else if (!sourceType.isAssignableTo(varargsComponentType) ||
420420
(sourceType.isArray() && !sourceType.isAssignableTo(varargsArrayType)) ||
421421
(argument instanceof List)) {
422422

423-
TypeDescriptor targetTypeToUse =
424-
(sourceType.isArray() || argument instanceof List ? varargsArrayType : varargsComponentType);
423+
TypeDescriptor targetTypeToUse = (sourceType.isArray() || argument instanceof List ||
424+
converter.canConvert(sourceType, varargsArrayType) ? varargsArrayType : varargsComponentType);
425425
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetTypeToUse);
426426
}
427427
// Possible outcomes of the above if-else block:

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -118,6 +118,11 @@ private static void populateMethodHandles(StandardEvaluationContext testContext)
118118
"varargsFunction", MethodType.methodType(String.class, String[].class));
119119
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);
120120

121+
// #varargsObjectFunctionHandle(args...)
122+
MethodHandle varargsObjectFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
123+
"varargsObjectFunction", MethodType.methodType(String.class, Object[].class));
124+
testContext.registerFunction("varargsObjectFunctionHandle", varargsObjectFunctionHandle);
125+
121126
// #add(int, int)
122127
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
123128
"add", MethodType.methodType(int.class, int.class, int.class));

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

+81-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@
1616

1717
package org.springframework.expression.spel;
1818

19+
import java.util.Set;
20+
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Nested;
1923
import org.junit.jupiter.api.Test;
2024

21-
import org.springframework.expression.spel.standard.SpelExpressionParser;
25+
import org.springframework.core.convert.TypeDescriptor;
26+
import org.springframework.core.convert.converter.GenericConverter;
27+
import org.springframework.core.convert.support.DefaultConversionService;
28+
import org.springframework.expression.Expression;
2229
import org.springframework.expression.spel.standard.SpelExpression;
2330
import org.springframework.expression.spel.support.StandardEvaluationContext;
31+
import org.springframework.expression.spel.support.StandardTypeConverter;
2432
import org.springframework.expression.spel.support.StandardTypeLocator;
33+
import org.springframework.lang.Nullable;
2534

2635
import static org.assertj.core.api.Assertions.assertThat;
2736
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -279,4 +288,75 @@ void functionMethodMustBeStatic() throws Exception {
279288
public void nonStatic() {
280289
}
281290

291+
292+
@Nested // gh-34371
293+
class VarargsAndPojoToArrayConversionTests {
294+
295+
private final StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
296+
297+
private final ArrayHolder arrayHolder = new ArrayHolder("a", "b", "c");
298+
299+
300+
@BeforeEach
301+
void setUp() {
302+
DefaultConversionService conversionService = new DefaultConversionService();
303+
conversionService.addConverter(new ArrayHolderConverter());
304+
context.setTypeConverter(new StandardTypeConverter(conversionService));
305+
context.setVariable("arrayHolder", arrayHolder);
306+
}
307+
308+
@Test
309+
void functionWithVarargsAndPojoToArrayConversion() {
310+
// #varargsFunction: static String varargsFunction(String... strings) -> Arrays.toString(strings)
311+
evaluate("#varargsFunction(#arrayHolder)", "[a, b, c]");
312+
313+
// #varargsObjectFunction: static String varargsObjectFunction(Object... args) -> Arrays.toString(args)
314+
//
315+
// Since ArrayHolder is an "instanceof Object" and Object is the varargs component type,
316+
// we expect the ArrayHolder not to be converted to an array but rather to be passed
317+
// "as is" as a single argument to the varargs method.
318+
evaluate("#varargsObjectFunction(#arrayHolder)", "[" + arrayHolder.toString() + "]");
319+
}
320+
321+
@Test
322+
void functionWithVarargsAndPojoToArrayConversionViaMethodHandle() {
323+
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)
324+
evaluate("#varargsFunctionHandle(#arrayHolder)", "[a, b, c]");
325+
326+
// #varargsObjectFunctionHandle: static String varargsObjectFunction(Object... args) -> Arrays.toString(args)
327+
//
328+
// Since ArrayHolder is an "instanceof Object" and Object is the varargs component type,
329+
// we expect the ArrayHolder not to be converted to an array but rather to be passed
330+
// "as is" as a single argument to the varargs method.
331+
evaluate("#varargsObjectFunctionHandle(#arrayHolder)", "[" + arrayHolder.toString() + "]");
332+
}
333+
334+
private void evaluate(String expression, Object expectedValue) {
335+
Expression expr = parser.parseExpression(expression);
336+
assertThat(expr).as("expression").isNotNull();
337+
Object value = expr.getValue(context);
338+
assertThat(value).as("expression '" + expression + "'").isEqualTo(expectedValue);
339+
}
340+
341+
342+
record ArrayHolder(String... array) {
343+
}
344+
345+
static class ArrayHolderConverter implements GenericConverter {
346+
347+
@Nullable
348+
@Override
349+
public Set<ConvertiblePair> getConvertibleTypes() {
350+
return Set.of(new ConvertiblePair(ArrayHolder.class, Object[].class));
351+
}
352+
353+
@Nullable
354+
@Override
355+
public String[] convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
356+
return ((ArrayHolder) source).array();
357+
}
358+
}
359+
360+
}
361+
282362
}

0 commit comments

Comments
 (0)