Document support for varargs invocations in SpEL
Closes gh-33332
This commit is contained in:
parent
5ac56bda87
commit
807e1e6126
|
@ -60,6 +60,7 @@
|
||||||
**** xref:core/expressions/language-ref/constructors.adoc[]
|
**** xref:core/expressions/language-ref/constructors.adoc[]
|
||||||
**** xref:core/expressions/language-ref/variables.adoc[]
|
**** xref:core/expressions/language-ref/variables.adoc[]
|
||||||
**** xref:core/expressions/language-ref/functions.adoc[]
|
**** xref:core/expressions/language-ref/functions.adoc[]
|
||||||
|
**** xref:core/expressions/language-ref/varargs.adoc[]
|
||||||
**** xref:core/expressions/language-ref/bean-references.adoc[]
|
**** xref:core/expressions/language-ref/bean-references.adoc[]
|
||||||
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
|
**** xref:core/expressions/language-ref/operator-ternary.adoc[]
|
||||||
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
|
**** xref:core/expressions/language-ref/operator-elvis.adoc[]
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
You can invoke constructors by using the `new` operator. You should use the fully
|
You can invoke constructors by using the `new` operator. You should use the fully
|
||||||
qualified class name for all types except those located in the `java.lang` package
|
qualified class name for all types except those located in the `java.lang` package
|
||||||
(`Integer`, `Float`, `String`, and so on). The following example shows how to use the
|
(`Integer`, `Float`, `String`, and so on).
|
||||||
`new` operator to invoke constructors:
|
xref:core/expressions/language-ref/varargs.adoc[Varargs] are also supported.
|
||||||
|
|
||||||
|
The following example shows how to use the `new` operator to invoke constructors.
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
@ -38,4 +40,3 @@ Kotlin::
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
= Functions
|
= Functions
|
||||||
|
|
||||||
You can extend SpEL by registering user-defined functions that can be called within
|
You can extend SpEL by registering user-defined functions that can be called within
|
||||||
expressions by using the `#functionName(...)` syntax. Functions can be registered as
|
expressions by using the `#functionName(...)` syntax, and like with standard method
|
||||||
variables in `EvaluationContext` implementations via the `setVariable()` method.
|
invocations, xref:core/expressions/language-ref/varargs.adoc[varargs] are also supported
|
||||||
|
for function invocations.
|
||||||
|
|
||||||
|
Functions can be registered as _variables_ in `EvaluationContext` implementations via the
|
||||||
|
`setVariable()` method.
|
||||||
|
|
||||||
[TIP]
|
[TIP]
|
||||||
====
|
====
|
||||||
|
@ -111,7 +115,8 @@ been fully bound prior to registration; however, partially bound handles are als
|
||||||
supported.
|
supported.
|
||||||
|
|
||||||
Consider the `String#formatted(Object...)` instance method, which produces a message
|
Consider the `String#formatted(Object...)` instance method, which produces a message
|
||||||
according to a template and a variable number of arguments.
|
according to a template and a variable number of arguments
|
||||||
|
(xref:core/expressions/language-ref/varargs.adoc[varargs]).
|
||||||
|
|
||||||
You can register and use the `formatted` method as a `MethodHandle`, as the following
|
You can register and use the `formatted` method as a `MethodHandle`, as the following
|
||||||
example shows:
|
example shows:
|
||||||
|
@ -203,4 +208,3 @@ Kotlin::
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
[[expressions-methods]]
|
[[expressions-methods]]
|
||||||
= Methods
|
= Methods
|
||||||
|
|
||||||
You can invoke methods by using typical Java programming syntax. You can also invoke methods
|
You can invoke methods by using the typical Java programming syntax. You can also invoke
|
||||||
on literals. Variable arguments are also supported. The following examples show how to
|
methods directly on literals such as strings or numbers.
|
||||||
invoke methods:
|
xref:core/expressions/language-ref/varargs.adoc[Varargs] are supported as well.
|
||||||
|
|
||||||
|
The following examples show how to invoke methods.
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
[[expressions-varargs]]
|
||||||
|
= Varargs Invocations
|
||||||
|
|
||||||
|
The Spring Expression Language supports
|
||||||
|
https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html[varargs]
|
||||||
|
invocations for xref:core/expressions/language-ref/constructors.adoc[constructors],
|
||||||
|
xref:core/expressions/language-ref/methods.adoc[methods], and user-defined
|
||||||
|
xref:core/expressions/language-ref/functions.adoc[functions].
|
||||||
|
|
||||||
|
The following example shows how to invoke the `java.lang.String#formatted(Object...)`
|
||||||
|
_varargs_ method within an expression by supplying the variable argument list as separate
|
||||||
|
arguments (`'blue', 1`).
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%d'.formatted('blue', 1)";
|
||||||
|
String message = parser.parseExpression(expression).getValue(String.class);
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
val expression = "'%s is color #%d'.formatted('blue', 1)"
|
||||||
|
val message = parser.parseExpression(expression).getValue(String::class.java)
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
A variable argument list can also be supplied as an array, as demonstrated in the
|
||||||
|
following example (`new Object[] {'blue', 1}`).
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})";
|
||||||
|
String message = parser.parseExpression(expression).getValue(String.class);
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
val expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})"
|
||||||
|
val message = parser.parseExpression(expression).getValue(String::class.java)
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
As an alternative, a variable argument list can be supplied as a `java.util.List` – for
|
||||||
|
example, as an xref:core/expressions/language-ref/inline-lists.adoc[inline list]
|
||||||
|
(`{'blue', 1}`). The following example shows how to do that.
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%d'.formatted({'blue', 1})";
|
||||||
|
String message = parser.parseExpression(expression).getValue(String.class);
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
val expression = "'%s is color #%d'.formatted({'blue', 1})"
|
||||||
|
val message = parser.parseExpression(expression).getValue(String::class.java)
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
[[expressions-varargs-type-conversion]]
|
||||||
|
== Varargs Type Conversion
|
||||||
|
|
||||||
|
In contrast to the standard support for varargs invocations in Java,
|
||||||
|
xref:core/expressions/evaluation.adoc#expressions-type-conversion[type conversion] may be
|
||||||
|
applied to the individual arguments when invoking varargs constructors, methods, or
|
||||||
|
functions in SpEL.
|
||||||
|
|
||||||
|
For example, if we have registered a custom
|
||||||
|
xref:core/expressions/language-ref/functions.adoc[function] in the `EvaluationContext`
|
||||||
|
under the name `#reverseStrings` for a method with the signature
|
||||||
|
`String reverseStrings(String... strings)`, we can invoke that function within a SpEL
|
||||||
|
expression with any argument that can be converted to a `String`, as demonstrated in the
|
||||||
|
following example.
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "3.0, 2.0, 1, SpEL"
|
||||||
|
String expression = "#reverseStrings('SpEL', 1, 10F / 5, 3.0000)";
|
||||||
|
String message = parser.parseExpression(expression)
|
||||||
|
.getValue(evaluationContext, String.class);
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "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)
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
Similarly, any array whose component type is a subtype of the required varargs type can
|
||||||
|
be supplied as the variable argument list for a varargs invocation. For example, a
|
||||||
|
`String[]` array can be supplied to a varargs invocation that accepts an `Object...`
|
||||||
|
argument list.
|
||||||
|
|
||||||
|
The following listing demonstrates that we can supply a `String[]` array to the
|
||||||
|
`java.lang.String#formatted(Object...)` _varargs_ method. It also highlights that `1`
|
||||||
|
will be automatically converted to `"1"`.
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})";
|
||||||
|
String message = parser.parseExpression(expression).getValue(String.class);
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
val expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})"
|
||||||
|
val message = parser.parseExpression(expression).getValue(String::class.java)
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
|
@ -21,10 +21,13 @@ import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import example.Color;
|
import example.Color;
|
||||||
import example.FruitMap;
|
import example.FruitMap;
|
||||||
|
@ -667,6 +670,59 @@ class SpelDocumentationTests extends AbstractExpressionTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class Varargs {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void varargsMethodInvocationWithIndividualArguments() {
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%d'.formatted('blue', 1)";
|
||||||
|
String message = parser.parseExpression(expression)
|
||||||
|
.getValue(String.class);
|
||||||
|
assertThat(message).isEqualTo("blue is color #1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void varargsMethodInvocationWithArgumentsAsObjectArray() {
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%d'.formatted(new Object[] {'blue', 1})";
|
||||||
|
String message = parser.parseExpression(expression)
|
||||||
|
.getValue(String.class);
|
||||||
|
assertThat(message).isEqualTo("blue is color #1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void varargsMethodInvocationWithArgumentsAsInlineList() {
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%d'.formatted({'blue', 1})";
|
||||||
|
String message = parser.parseExpression(expression).getValue(String.class);
|
||||||
|
assertThat(message).isEqualTo("blue is color #1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void varargsMethodInvocationWithTypeConversion() {
|
||||||
|
Method reverseStringsMethod = ReflectionUtils.findMethod(StringUtils.class, "reverseStrings", String[].class);
|
||||||
|
SimpleEvaluationContext evaluationContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||||
|
evaluationContext.setVariable("reverseStrings", reverseStringsMethod);
|
||||||
|
|
||||||
|
// String reverseStrings(String... strings)
|
||||||
|
// evaluates to "3.0, 2.0, 1.0, SpEL"
|
||||||
|
String expression = "#reverseStrings('SpEL', 1, 10F / 5, 3.0000)";
|
||||||
|
String message = parser.parseExpression(expression)
|
||||||
|
.getValue(evaluationContext, String.class);
|
||||||
|
assertThat(message).isEqualTo("3.0, 2.0, 1, SpEL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void varargsMethodInvocationWithArgumentsAsStringArray() {
|
||||||
|
// evaluates to "blue is color #1"
|
||||||
|
String expression = "'%s is color #%s'.formatted(new String[] {'blue', 1})";
|
||||||
|
String message = parser.parseExpression(expression).getValue(String.class);
|
||||||
|
assertThat(message).isEqualTo("blue is color #1");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
class TernaryOperator {
|
class TernaryOperator {
|
||||||
|
|
||||||
|
@ -906,6 +962,12 @@ class SpelDocumentationTests extends AbstractExpressionTests {
|
||||||
public static String reverseString(String input) {
|
public static String reverseString(String input) {
|
||||||
return new StringBuilder(input).reverse().toString();
|
return new StringBuilder(input).reverse().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String reverseStrings(String... strings) {
|
||||||
|
List<String> list = Arrays.asList(strings);
|
||||||
|
Collections.reverse(list);
|
||||||
|
return list.stream().collect(Collectors.joining(", "));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ListConcatenation implements OperatorOverloader {
|
private static class ListConcatenation implements OperatorOverloader {
|
||||||
|
|
Loading…
Reference in New Issue