Browse Source

Document that "functions are variables" in SpEL evaluation contexts

Although EvaluationContext defines the API for setting and looking up
variables, the internals of the Spring Expression Language (SpEL)
actually provide explicit support for registering functions as
variables.

This is self-evident in the two registerFunction() variants in
StandardEvaluationContext; however, functions can also be registered as
variables when using the SimpleEvaluationContext.

Since custom functions are also viable in use cases involving the
SimpleEvaluationContext, this commit documents that functions may be
registered in a SimpleEvaluationContext via setVariable().

This commit also explicitly documents the "function as a variable"
behavior in the class-level Javadoc for both StandardEvaluationContext
and SimpleEvaluationContext, as well as in the reference manual.

Closes gh-32258
pull/32263/head
Sam Brannen 2 years ago
parent
commit
68189f3de9
  1. 24
      framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc
  2. 7
      framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc
  3. 28
      spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java
  4. 56
      spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java

24
framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc

@ -1,10 +1,26 @@ @@ -1,10 +1,26 @@
[[expressions-ref-functions]]
= Functions
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 to be invoked via reflection
(i.e. a `Method`):
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.
[TIP]
====
`StandardEvaluationContext` also defines `registerFunction(...)` methods that provide a
convenient way to register a function as a `java.lang.reflect.Method` or a
`java.lang.invoke.MethodHandle`.
====
[WARNING]
====
Since functions share a common namespace with
xref:core/expressions/language-ref/variables.adoc[variables] in the evaluation context,
care must be taken to ensure that function names and variable names do not overlap.
====
The following example shows how to register a user-defined function to be invoked via
reflection using a `java.lang.reflect.Method`:
[tabs]
======

7
framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc

@ -20,6 +20,13 @@ characters. @@ -20,6 +20,13 @@ characters.
* dollar sign: `$`
====
[WARNING]
====
Since variables share a common namespace with
xref:core/expressions/language-ref/functions.adoc[functions] in the evaluation context,
care must be taken to ensure that variable names and functions names do not overlap.
====
The following example shows how to use variables.
[tabs]

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

@ -74,6 +74,14 @@ import org.springframework.lang.Nullable; @@ -74,6 +74,14 @@ import org.springframework.lang.Nullable;
* {@code EvaluationContext} and a root object as arguments:
* {@link org.springframework.expression.Expression#getValue(EvaluationContext, Object)}.
*
* <p>In addition to support for setting and looking up variables as defined in
* the {@link EvaluationContext} API, {@code SimpleEvaluationContext} also
* provides support for {@linkplain #setVariable(String, Object) registering} and
* {@linkplain #lookupVariable(String) looking up} functions as variables. Since
* functions share a common namespace with the variables in this evaluation
* context, care must be taken to ensure that function names and variable names
* do not overlap.
*
* <p>For more power and flexibility, in particular for internal configuration
* scenarios, consider using {@link StandardEvaluationContext} instead.
*
@ -214,11 +222,31 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -214,11 +222,31 @@ public final class SimpleEvaluationContext implements EvaluationContext {
throw new SpelEvaluationException(SpelMessage.VARIABLE_ASSIGNMENT_NOT_SUPPORTED, "#" + name);
}
/**
* Set a named variable or function in this evaluation context to the specified
* value.
* <p>A function can be registered as a {@link java.lang.reflect.Method} or
* a {@link java.lang.invoke.MethodHandle}.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain SimpleEvaluationContext
* class-level documentation} for details.
* @param name the name of the variable or function to set
* @param value the value to be placed in the variable or function
* @see #lookupVariable(String)
*/
@Override
public void setVariable(String name, @Nullable Object value) {
this.variables.put(name, value);
}
/**
* Look up a named variable or function within this evaluation context.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain SimpleEvaluationContext
* class-level documentation} for details.
* @param name the name of the variable or function to look up
* @return the value of the variable or function, or {@code null} if not found
*/
@Override
@Nullable
public Object lookupVariable(String name) {

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

@ -48,6 +48,16 @@ import org.springframework.util.Assert; @@ -48,6 +48,16 @@ import org.springframework.util.Assert;
* to reliably locate user types. See {@link #setTypeLocator(TypeLocator)} for
* details.
*
* <p>In addition to support for setting and looking up variables as defined in
* the {@link EvaluationContext} API, {@code StandardEvaluationContext} also
* provides support for registering and looking up functions. The
* {@code registerFunction(...)} methods provide a convenient way to register a
* function as a {@link Method} or a {@link MethodHandle}; however, a function
* can also be registered via {@link #setVariable(String, Object)} or
* {@link #setVariables(Map)}. Since functions share a namespace with the variables
* in this evaluation context, care must be taken to ensure that function names
* and variable names do not overlap.
*
* <p>For a simpler, builder-style context variant for data-binding purposes,
* consider using {@link SimpleEvaluationContext} instead which allows for
* opting into several SpEL features as needed by specific use cases.
@ -253,6 +263,25 @@ public class StandardEvaluationContext implements EvaluationContext { @@ -253,6 +263,25 @@ public class StandardEvaluationContext implements EvaluationContext {
return this.operatorOverloader;
}
/**
* Set a named variable in this evaluation context to a specified value.
* <p>If the specified {@code name} is {@code null}, it will be ignored. If
* the specified {@code value} is {@code null}, the named variable will be
* removed from this evaluation context.
* <p>In contrast to {@link #assignVariable(String,java.util.function.Supplier)},
* this method should only be invoked programmatically when interacting directly
* with the {@code EvaluationContext} &mdash; for example, to provide initial
* configuration for the context.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain StandardEvaluationContext
* class-level documentation} for details.
* @param name the name of the variable to set
* @param value the value to be placed in the variable
* @see #setVariables(Map)
* @see #registerFunction(String, Method)
* @see #registerFunction(String, MethodHandle)
* @see #lookupVariable(String)
*/
@Override
public void setVariable(@Nullable String name, @Nullable Object value) {
// For backwards compatibility, we ignore null names here...
@ -271,6 +300,9 @@ public class StandardEvaluationContext implements EvaluationContext { @@ -271,6 +300,9 @@ public class StandardEvaluationContext implements EvaluationContext {
/**
* Set multiple named variables in this evaluation context to the specified values.
* <p>This is a convenience variant of {@link #setVariable(String, Object)}.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain StandardEvaluationContext
* class-level documentation} for details.
* @param variables the names and values of the variables to set
* @see #setVariable(String, Object)
*/
@ -280,9 +312,9 @@ public class StandardEvaluationContext implements EvaluationContext { @@ -280,9 +312,9 @@ public class StandardEvaluationContext implements EvaluationContext {
/**
* Register the specified {@link Method} as a SpEL function.
* <p>Note: Function names share a namespace with the variables in this
* evaluation context, as populated by {@link #setVariable(String, Object)}.
* Make sure that specified function names and variable names do not overlap.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain StandardEvaluationContext
* class-level documentation} for details.
* @param name the name of the function
* @param method the {@code Method} to register
* @see #registerFunction(String, MethodHandle)
@ -293,9 +325,9 @@ public class StandardEvaluationContext implements EvaluationContext { @@ -293,9 +325,9 @@ public class StandardEvaluationContext implements EvaluationContext {
/**
* Register the specified {@link MethodHandle} as a SpEL function.
* <p>Note: Function names share a namespace with the variables in this
* evaluation context, as populated by {@link #setVariable(String, Object)}.
* Make sure that specified function names and variable names do not overlap.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain StandardEvaluationContext
* class-level documentation} for details.
* @param name the name of the function
* @param methodHandle the {@link MethodHandle} to register
* @since 6.1
@ -305,6 +337,14 @@ public class StandardEvaluationContext implements EvaluationContext { @@ -305,6 +337,14 @@ public class StandardEvaluationContext implements EvaluationContext {
this.variables.put(name, methodHandle);
}
/**
* Look up a named variable or function within this evaluation context.
* <p>Note that variables and functions share a common namespace in this
* evaluation context. See the {@linkplain StandardEvaluationContext
* class-level documentation} for details.
* @param name the name of the variable or function to look up
* @return the value of the variable or function, or {@code null} if not found
*/
@Override
@Nullable
public Object lookupVariable(String name) {
@ -333,9 +373,9 @@ public class StandardEvaluationContext implements EvaluationContext { @@ -333,9 +373,9 @@ public class StandardEvaluationContext implements EvaluationContext {
/**
* Apply the internal delegates of this instance to the specified
* {@code evaluationContext}. Typically invoked right after the new context
* instance has been created to reuse the delegates. Do not modify the
* instance has been created to reuse the delegates. Does not modify the
* {@linkplain #setRootObject(Object) root object} or any registered
* {@linkplain #setVariables(Map) variables}.
* {@linkplain #setVariable variables or functions}.
* @param evaluationContext the evaluation context to update
* @since 6.1.1
*/

Loading…
Cancel
Save