Skip to content

Commit 68189f3

Browse files
committed
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
1 parent 10bc93c commit 68189f3

File tree

4 files changed

+103
-12
lines changed

4 files changed

+103
-12
lines changed

Diff for: framework-docs/modules/ROOT/pages/core/expressions/language-ref/functions.adoc

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
[[expressions-ref-functions]]
22
= Functions
33

4-
You can extend SpEL by registering user-defined functions that can be called within the
5-
expression string. The function is registered through the `EvaluationContext`. The
6-
following example shows how to register a user-defined function to be invoked via reflection
7-
(i.e. a `Method`):
4+
You can extend SpEL by registering user-defined functions that can be called within
5+
expressions by using the `#functionName(...)` syntax. Functions can be registered as
6+
variables in `EvaluationContext` implementations via the `setVariable()` method.
7+
8+
[TIP]
9+
====
10+
`StandardEvaluationContext` also defines `registerFunction(...)` methods that provide a
11+
convenient way to register a function as a `java.lang.reflect.Method` or a
12+
`java.lang.invoke.MethodHandle`.
13+
====
14+
15+
[WARNING]
16+
====
17+
Since functions share a common namespace with
18+
xref:core/expressions/language-ref/variables.adoc[variables] in the evaluation context,
19+
care must be taken to ensure that function names and variable names do not overlap.
20+
====
21+
22+
The following example shows how to register a user-defined function to be invoked via
23+
reflection using a `java.lang.reflect.Method`:
824

925
[tabs]
1026
======

Diff for: framework-docs/modules/ROOT/pages/core/expressions/language-ref/variables.adoc

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ characters.
2020
* dollar sign: `$`
2121
====
2222

23+
[WARNING]
24+
====
25+
Since variables share a common namespace with
26+
xref:core/expressions/language-ref/functions.adoc[functions] in the evaluation context,
27+
care must be taken to ensure that variable names and functions names do not overlap.
28+
====
29+
2330
The following example shows how to use variables.
2431

2532
[tabs]

Diff for: spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java

+28
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@
7474
* {@code EvaluationContext} and a root object as arguments:
7575
* {@link org.springframework.expression.Expression#getValue(EvaluationContext, Object)}.
7676
*
77+
* <p>In addition to support for setting and looking up variables as defined in
78+
* the {@link EvaluationContext} API, {@code SimpleEvaluationContext} also
79+
* provides support for {@linkplain #setVariable(String, Object) registering} and
80+
* {@linkplain #lookupVariable(String) looking up} functions as variables. Since
81+
* functions share a common namespace with the variables in this evaluation
82+
* context, care must be taken to ensure that function names and variable names
83+
* do not overlap.
84+
*
7785
* <p>For more power and flexibility, in particular for internal configuration
7886
* scenarios, consider using {@link StandardEvaluationContext} instead.
7987
*
@@ -214,11 +222,31 @@ public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier
214222
throw new SpelEvaluationException(SpelMessage.VARIABLE_ASSIGNMENT_NOT_SUPPORTED, "#" + name);
215223
}
216224

225+
/**
226+
* Set a named variable or function in this evaluation context to the specified
227+
* value.
228+
* <p>A function can be registered as a {@link java.lang.reflect.Method} or
229+
* a {@link java.lang.invoke.MethodHandle}.
230+
* <p>Note that variables and functions share a common namespace in this
231+
* evaluation context. See the {@linkplain SimpleEvaluationContext
232+
* class-level documentation} for details.
233+
* @param name the name of the variable or function to set
234+
* @param value the value to be placed in the variable or function
235+
* @see #lookupVariable(String)
236+
*/
217237
@Override
218238
public void setVariable(String name, @Nullable Object value) {
219239
this.variables.put(name, value);
220240
}
221241

242+
/**
243+
* Look up a named variable or function within this evaluation context.
244+
* <p>Note that variables and functions share a common namespace in this
245+
* evaluation context. See the {@linkplain SimpleEvaluationContext
246+
* class-level documentation} for details.
247+
* @param name the name of the variable or function to look up
248+
* @return the value of the variable or function, or {@code null} if not found
249+
*/
222250
@Override
223251
@Nullable
224252
public Object lookupVariable(String name) {

Diff for: spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java

+48-8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@
4848
* to reliably locate user types. See {@link #setTypeLocator(TypeLocator)} for
4949
* details.
5050
*
51+
* <p>In addition to support for setting and looking up variables as defined in
52+
* the {@link EvaluationContext} API, {@code StandardEvaluationContext} also
53+
* provides support for registering and looking up functions. The
54+
* {@code registerFunction(...)} methods provide a convenient way to register a
55+
* function as a {@link Method} or a {@link MethodHandle}; however, a function
56+
* can also be registered via {@link #setVariable(String, Object)} or
57+
* {@link #setVariables(Map)}. Since functions share a namespace with the variables
58+
* in this evaluation context, care must be taken to ensure that function names
59+
* and variable names do not overlap.
60+
*
5161
* <p>For a simpler, builder-style context variant for data-binding purposes,
5262
* consider using {@link SimpleEvaluationContext} instead which allows for
5363
* opting into several SpEL features as needed by specific use cases.
@@ -253,6 +263,25 @@ public OperatorOverloader getOperatorOverloader() {
253263
return this.operatorOverloader;
254264
}
255265

266+
/**
267+
* Set a named variable in this evaluation context to a specified value.
268+
* <p>If the specified {@code name} is {@code null}, it will be ignored. If
269+
* the specified {@code value} is {@code null}, the named variable will be
270+
* removed from this evaluation context.
271+
* <p>In contrast to {@link #assignVariable(String,java.util.function.Supplier)},
272+
* this method should only be invoked programmatically when interacting directly
273+
* with the {@code EvaluationContext} &mdash; for example, to provide initial
274+
* configuration for the context.
275+
* <p>Note that variables and functions share a common namespace in this
276+
* evaluation context. See the {@linkplain StandardEvaluationContext
277+
* class-level documentation} for details.
278+
* @param name the name of the variable to set
279+
* @param value the value to be placed in the variable
280+
* @see #setVariables(Map)
281+
* @see #registerFunction(String, Method)
282+
* @see #registerFunction(String, MethodHandle)
283+
* @see #lookupVariable(String)
284+
*/
256285
@Override
257286
public void setVariable(@Nullable String name, @Nullable Object value) {
258287
// For backwards compatibility, we ignore null names here...
@@ -271,6 +300,9 @@ public void setVariable(@Nullable String name, @Nullable Object value) {
271300
/**
272301
* Set multiple named variables in this evaluation context to the specified values.
273302
* <p>This is a convenience variant of {@link #setVariable(String, Object)}.
303+
* <p>Note that variables and functions share a common namespace in this
304+
* evaluation context. See the {@linkplain StandardEvaluationContext
305+
* class-level documentation} for details.
274306
* @param variables the names and values of the variables to set
275307
* @see #setVariable(String, Object)
276308
*/
@@ -280,9 +312,9 @@ public void setVariables(Map<String, Object> variables) {
280312

281313
/**
282314
* Register the specified {@link Method} as a SpEL function.
283-
* <p>Note: Function names share a namespace with the variables in this
284-
* evaluation context, as populated by {@link #setVariable(String, Object)}.
285-
* Make sure that specified function names and variable names do not overlap.
315+
* <p>Note that variables and functions share a common namespace in this
316+
* evaluation context. See the {@linkplain StandardEvaluationContext
317+
* class-level documentation} for details.
286318
* @param name the name of the function
287319
* @param method the {@code Method} to register
288320
* @see #registerFunction(String, MethodHandle)
@@ -293,9 +325,9 @@ public void registerFunction(String name, Method method) {
293325

294326
/**
295327
* Register the specified {@link MethodHandle} as a SpEL function.
296-
* <p>Note: Function names share a namespace with the variables in this
297-
* evaluation context, as populated by {@link #setVariable(String, Object)}.
298-
* Make sure that specified function names and variable names do not overlap.
328+
* <p>Note that variables and functions share a common namespace in this
329+
* evaluation context. See the {@linkplain StandardEvaluationContext
330+
* class-level documentation} for details.
299331
* @param name the name of the function
300332
* @param methodHandle the {@link MethodHandle} to register
301333
* @since 6.1
@@ -305,6 +337,14 @@ public void registerFunction(String name, MethodHandle methodHandle) {
305337
this.variables.put(name, methodHandle);
306338
}
307339

340+
/**
341+
* Look up a named variable or function within this evaluation context.
342+
* <p>Note that variables and functions share a common namespace in this
343+
* evaluation context. See the {@linkplain StandardEvaluationContext
344+
* class-level documentation} for details.
345+
* @param name the name of the variable or function to look up
346+
* @return the value of the variable or function, or {@code null} if not found
347+
*/
308348
@Override
309349
@Nullable
310350
public Object lookupVariable(String name) {
@@ -333,9 +373,9 @@ public void registerMethodFilter(Class<?> type, MethodFilter filter) throws Ille
333373
/**
334374
* Apply the internal delegates of this instance to the specified
335375
* {@code evaluationContext}. Typically invoked right after the new context
336-
* instance has been created to reuse the delegates. Do not modify the
376+
* instance has been created to reuse the delegates. Does not modify the
337377
* {@linkplain #setRootObject(Object) root object} or any registered
338-
* {@linkplain #setVariables(Map) variables}.
378+
* {@linkplain #setVariable variables or functions}.
339379
* @param evaluationContext the evaluation context to update
340380
* @since 6.1.1
341381
*/

0 commit comments

Comments
 (0)