可变参数调用 (Varargs Invocations)
Spring 表达式语言支持对构造函数、方法和用户定义的函数进行可变参数(varargs)调用。
以下示例展示了如何在表达式中调用 java.lang.String#formatted(Object…) 可变参数方法,通过平铺方式提供参数列表('blue', 1):
// 评估结果为 "blue is color #1"
String expression = "'%s is color #%d'.formatted('blue', 1)";
String message = parser.parseExpression(expression).getValue(String.class);// 评估结果为 "blue is color #1"
val expression = "'%s is color #%d'.formatted('blue', 1)"
val message = parser.parseExpression(expression).getValue(String::class.java)可变参数列表也可以通过数组形式提供,如下例所示(使用 new Object[] {'blue', 1}):
// 评估结果为 "blue is color #1"
String expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})";
String message = parser.parseExpression(expression).getValue(String.class);// 评估结果为 "blue is color #1"
val expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})"
val message = parser.parseExpression(expression).getValue(String::class.java)作为另一种选择,可变参数列表还可以通过 java.util.List 提供——例如,使用内联列表 (Inline List)({'blue', 1})。下例展示了具体做法:
// 评估结果为 "blue is color #1"
String expression = "'%s is color #%d'.formatted({'blue', 1})";
String message = parser.parseExpression(expression).getValue(String.class);// 评估结果为 "blue is color #1"
val expression = "'%s is color #%d'.formatted({'blue', 1})"
val message = parser.parseExpression(expression).getValue(String::class.java)可变参数的类型转换 (Varargs Type Conversion)
与 Java 中对可变参数调用的标准支持不同,在 SpEL 中调用可变参数的构造函数、方法或函数时,可以对 单个参数 应用类型转换。
例如,如果我们注册了一个名为 #reverseStrings 的自定义函数,其方法签名是 String reverseStrings(String… strings),那么我们可以使用任何能够转换为 String 的参数来调用该函数:
// 评估结果为 "3.0, 2.0, 1, SpEL"
// 注意:1, 2.0, 3.0000 都会被自动转换为 String
String expression = "#reverseStrings('SpEL', 1, 10F / 5, 3.0000)";
String message = parser.parseExpression(expression)
.getValue(evaluationContext, String.class);// 评估结果为 "3.0, 2.0, 1, SpEL"
val expression = "#reverseStrings('SpEL', 1, 10F / 5, 3.0000)"
val message = parser.parseExpression(expression)
.getValue(evaluationContext, String::class.java)同样,任何组件类型(Component Type)是所需可变参数类型子类型的数组,也可以作为参数列表。例如,String[] 数组可以提供给接受 Object… 参数的方法。
下例展示了我们可以将 String[] 数组传递给 java.lang.String#formatted(Object…) 方法。同时通过该例可见,数字 1 会被自动转换为字符串 "1" 以适配 String[]:
// 评估结果为 "blue is color #1"
String expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})";
String message = parser.parseExpression(expression).getValue(String.class);// 评估结果为 "blue is color #1"
val expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})"
val message = parser.parseExpression(expression).getValue(String::class.java)补充教学
1. SpEL 比 Java 编译器“更聪明”吗?
从某种程度上说是的。在原生 Java 中,如果你把一个 int 传给一个要求 String... 的方法,编译器会直接报错。
- SpEL 的优势:SpEL 利用 Spring 底层的 ConversionService。它知道如何将数字、布尔值甚至是复杂的自定义对象转换成目标方法所需的类型。这种宽松的限制使得编写动态表达式变得非常轻松。
2. 内联列表 {}:可变参数的绝佳拍档
在 Java 中,如果你想动态传入一个参数列表,你通常需要 new Object[] { ... }。 但是在 SpEL 中,你可以直接使用内联列表语法:
- 语法:
#func({'a', 'b', 'c'}) - 语义:SpEL 会识别出目标方法需要的是可变参数数组,并自动将这个
List转为数组。这比写数组构造语法要清爽得多。
3. 可变参数调用的优先级
如果你定义了两个重载方法:
doSomething(String s)doSomething(String... s)如果你在表达式中写doSomething('test'),SpEL 的解析引擎会遵循 Java 的规范,优先匹配 非可变参数 的版本。
4. 性能考量:避免大规模隐式转换
虽然类型转换很方便,但在可变参数调用中,每个参数都需要经过转换器的判定和执行。
- 优化建议:如果你在执行一个高频调用的循环逻辑,且参数数量较多,尽量在传入表达式之前就处理好类型,或者确保参数类型与方法定义完全一致,以绕过 ConversionService 的查找开销。