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/variables.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/operator-ternary.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
|
||||
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
|
||||
`new` operator to invoke constructors:
|
||||
(`Integer`, `Float`, `String`, and so on).
|
||||
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]
|
||||
======
|
||||
|
@ -38,4 +40,3 @@ Kotlin::
|
|||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
= Functions
|
||||
|
||||
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
|
||||
variables in `EvaluationContext` implementations via the `setVariable()` method.
|
||||
expressions by using the `#functionName(...)` syntax, and like with standard 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]
|
||||
====
|
||||
|
@ -111,7 +115,8 @@ been fully bound prior to registration; however, partially bound handles are als
|
|||
supported.
|
||||
|
||||
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
|
||||
example shows:
|
||||
|
@ -203,4 +208,3 @@ Kotlin::
|
|||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
[[expressions-methods]]
|
||||
= Methods
|
||||
|
||||
You can invoke methods by using typical Java programming syntax. You can also invoke methods
|
||||
on literals. Variable arguments are also supported. The following examples show how to
|
||||
invoke methods:
|
||||
You can invoke methods by using the typical Java programming syntax. You can also invoke
|
||||
methods directly on literals such as strings or numbers.
|
||||
xref:core/expressions/language-ref/varargs.adoc[Varargs] are supported as well.
|
||||
|
||||
The following examples show how to invoke methods.
|
||||
|
||||
[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.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import example.Color;
|
||||
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
|
||||
class TernaryOperator {
|
||||
|
||||
|
@ -906,6 +962,12 @@ class SpelDocumentationTests extends AbstractExpressionTests {
|
|||
public static String reverseString(String input) {
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue