Add SpEL support for registered MethodHandles
This commit adds support for MethodHandles in SpEL, using the same syntax as user-defined functions (which also covers reflective Methods). The most benefit is expected with handles that capture a static method with no arguments, or with fully bound handles (where all the arguments have been bound, including a target instance as first bound argument if necessary). Partially bound MethodHandle should also be supported. A best effort approach is taken to detect varargs as there is no API support to determine if an argument is a vararg or an explicit array, unlike with Method. Argument conversions are also applied. Finally, array repacking is not always necessary with varargs so it is only performed when the vararg is the sole argument to the invoked method. See gh-27099 Closes gh-30045
This commit is contained in:
@@ -47,7 +47,9 @@ The expression language supports the following functionality:
|
||||
* Inline maps
|
||||
* Ternary operator
|
||||
* Variables
|
||||
* User-defined functions
|
||||
* User-defined functions added to the context
|
||||
* reflective invocation of `Method`
|
||||
* various cases of `MethodHandle`
|
||||
* Collection projection
|
||||
* Collection selection
|
||||
* Templated expressions
|
||||
|
||||
@@ -15,7 +15,7 @@ topics:
|
||||
* xref:core/expressions/language-ref/types.adoc[Types]
|
||||
* xref:core/expressions/language-ref/constructors.adoc[Constructors]
|
||||
* xref:core/expressions/language-ref/variables.adoc[Variables]
|
||||
* xref:core/expressions/language-ref/functions.adoc[Functions]
|
||||
* xref:core/expressions/language-ref/functions.adoc[User-Defined Functions]
|
||||
* xref:core/expressions/language-ref/bean-references.adoc[Bean References]
|
||||
* xref:core/expressions/language-ref/operator-ternary.adoc[Ternary Operator (If-Then-Else)]
|
||||
* xref:core/expressions/language-ref/operator-elvis.adoc[The Elvis Operator]
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
You can extend SpEL by registering user-defined functions that can be called within the
|
||||
expression string. The function is registered through the `EvaluationContext`. The
|
||||
following example shows how to register a user-defined function:
|
||||
following example shows how to register a user-defined function to be invoked via reflection
|
||||
(i.e. a `Method`):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -94,5 +95,97 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The use of `MethodHandle` is also supported. This enables potentially more efficient use
|
||||
cases if the `MethodHandle` target and parameters have been fully bound prior to
|
||||
registration, but partially bound handles are also supported.
|
||||
|
||||
Consider the `String#formatted(String, Object...)` instance method, which produces a
|
||||
message according to a template and a variable number of arguments.
|
||||
|
||||
You can register and use the `formatted` method as a `MethodHandle`, as the following
|
||||
example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class));
|
||||
context.setVariable("message", mh);
|
||||
|
||||
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String.class);
|
||||
//returns "Simple message: <Hello World>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
context.setVariable("message", mh)
|
||||
|
||||
val message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
As hinted above, binding a `MethodHandle` and registering the bound `MethodHandle` is also
|
||||
supported. This is likely to be more performant if both the target and all the arguments
|
||||
are bound. In that case no arguments are necessary in the SpEL expression, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
String template = "This is a %s message with %s words: <%s>";
|
||||
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs); //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh);
|
||||
|
||||
String message = parser.parseExpression("#message()")
|
||||
.getValue(context, String.class);
|
||||
//returns "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
val template = "This is a %s message with %s words: <%s>"
|
||||
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")
|
||||
|
||||
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs) //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh)
|
||||
|
||||
val message = parser.parseExpression("#message()")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user