Revise SpEL Evaluation documentation

This commit is contained in:
Sam Brannen 2024-01-09 12:03:28 +01:00
parent 4d11307b84
commit b823c46aae
1 changed files with 68 additions and 70 deletions

View File

@ -1,12 +1,12 @@
[[expressions-evaluation]] [[expressions-evaluation]]
= Evaluation = Evaluation
This section introduces the simple use of SpEL interfaces and its expression language. This section introduces programmatic use of SpEL's interfaces and its expression language.
The complete language reference can be found in The complete language reference can be found in the
xref:core/expressions/language-ref.adoc[Language Reference]. xref:core/expressions/language-ref.adoc[Language Reference].
The following code introduces the SpEL API to evaluate the literal string expression, The following code demonstrates how to use the SpEL API to evaluate the literal string
`Hello World`. expression, `Hello World`.
[tabs] [tabs]
====== ======
@ -31,21 +31,21 @@ Kotlin::
<1> The value of the message variable is `'Hello World'`. <1> The value of the message variable is `'Hello World'`.
====== ======
The SpEL classes and interfaces you are most likely to use are located in the The SpEL classes and interfaces you are most likely to use are located in the
`org.springframework.expression` package and its sub-packages, such as `spel.support`. `org.springframework.expression` package and its sub-packages, such as `spel.support`.
The `ExpressionParser` interface is responsible for parsing an expression string. In The `ExpressionParser` interface is responsible for parsing an expression string. In the
the preceding example, the expression string is a string literal denoted by the surrounding single preceding example, the expression string is a string literal denoted by the surrounding
quotation marks. The `Expression` interface is responsible for evaluating the previously defined single quotation marks. The `Expression` interface is responsible for evaluating the
expression string. Two exceptions that can be thrown, `ParseException` and defined expression string. The two types of exceptions that can be thrown when calling
`EvaluationException`, when calling `parser.parseExpression` and `exp.getValue`, `parser.parseExpression(...)` and `exp.getValue(...)` are `ParseException` and
respectively. `EvaluationException`, respectively.
SpEL supports a wide range of features, such as calling methods, accessing properties, SpEL supports a wide range of features such as calling methods, accessing properties,
and calling constructors. and calling constructors.
In the following example of method invocation, we call the `concat` method on the string literal: In the following method invocation example, we call the `concat` method on the string
literal, `Hello World`.
[tabs] [tabs]
====== ======
@ -70,7 +70,8 @@ Kotlin::
<1> The value of `message` is now 'Hello World!'. <1> The value of `message` is now 'Hello World!'.
====== ======
The following example of calling a JavaBean property calls the `String` property `Bytes`: The following example demonstrates how to access the `Bytes` JavaBean property of the
string literal, `Hello World`.
[tabs] [tabs]
====== ======
@ -100,10 +101,10 @@ Kotlin::
====== ======
SpEL also supports nested properties by using the standard dot notation (such as SpEL also supports nested properties by using the standard dot notation (such as
`prop1.prop2.prop3`) and also the corresponding setting of property values. `prop1.prop2.prop3`) as well as the corresponding setting of property values.
Public fields may also be accessed. Public fields may also be accessed.
The following example shows how to use dot notation to get the length of a literal: The following example shows how to use dot notation to get the length of a string literal.
[tabs] [tabs]
====== ======
@ -133,7 +134,7 @@ Kotlin::
====== ======
The String's constructor can be called instead of using a string literal, as the following The String's constructor can be called instead of using a string literal, as the following
example shows: example shows.
[tabs] [tabs]
====== ======
@ -145,7 +146,7 @@ Java::
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1> Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1>
String message = exp.getValue(String.class); String message = exp.getValue(String.class);
---- ----
<1> Construct a new `String` from the literal and make it be upper case. <1> Construct a new `String` from the literal and convert it to upper case.
Kotlin:: Kotlin::
+ +
@ -155,10 +156,9 @@ Kotlin::
val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1> val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1>
val message = exp.getValue(String::class.java) val message = exp.getValue(String::class.java)
---- ----
<1> Construct a new `String` from the literal and make it be upper case. <1> Construct a new `String` from the literal and convert it to upper case.
====== ======
Note the use of the generic method: `public <T> T getValue(Class<T> desiredResultType)`. Note the use of the generic method: `public <T> T getValue(Class<T> desiredResultType)`.
Using this method removes the need to cast the value of the expression to the desired Using this method removes the need to cast the value of the expression to the desired
result type. An `EvaluationException` is thrown if the value cannot be cast to the result type. An `EvaluationException` is thrown if the value cannot be cast to the
@ -166,8 +166,8 @@ type `T` or converted by using the registered type converter.
The more common usage of SpEL is to provide an expression string that is evaluated The more common usage of SpEL is to provide an expression string that is evaluated
against a specific object instance (called the root object). The following example shows against a specific object instance (called the root object). The following example shows
how to retrieve the `name` property from an instance of the `Inventor` class or how to retrieve the `name` property from an instance of the `Inventor` class and how to
create a boolean condition: reference the `name` property in a boolean expression.
[tabs] [tabs]
====== ======
@ -240,7 +240,7 @@ It excludes Java type references, constructors, and bean references. It also req
you to explicitly choose the level of support for properties and methods in expressions. you to explicitly choose the level of support for properties and methods in expressions.
By default, the `create()` static factory method enables only read access to properties. By default, the `create()` static factory method enables only read access to properties.
You can also obtain a builder to configure the exact level of support needed, targeting You can also obtain a builder to configure the exact level of support needed, targeting
one or some combination of the following: one or some combination of the following.
* Custom `PropertyAccessor` only (no reflection) * Custom `PropertyAccessor` only (no reflection)
* Data binding properties for read-only access * Data binding properties for read-only access
@ -252,16 +252,15 @@ one or some combination of the following:
By default, SpEL uses the conversion service available in Spring core By default, SpEL uses the conversion service available in Spring core
(`org.springframework.core.convert.ConversionService`). This conversion service comes (`org.springframework.core.convert.ConversionService`). This conversion service comes
with many built-in converters for common conversions but is also fully extensible so that with many built-in converters for common conversions, but is also fully extensible so
you can add custom conversions between types. Additionally, it is that you can add custom conversions between types. Additionally, it is generics-aware.
generics-aware. This means that, when you work with generic types in This means that, when you work with generic types in expressions, SpEL attempts
expressions, SpEL attempts conversions to maintain type correctness for any objects conversions to maintain type correctness for any objects it encounters.
it encounters.
What does this mean in practice? Suppose assignment, using `setValue()`, is being used What does this mean in practice? Suppose assignment, using `setValue()`, is being used
to set a `List` property. The type of the property is actually `List<Boolean>`. SpEL to set a `List` property. The type of the property is actually `List<Boolean>`. SpEL
recognizes that the elements of the list need to be converted to `Boolean` before recognizes that the elements of the list need to be converted to `Boolean` before
being placed in it. The following example shows how to do so: being placed in it. The following example shows how to do so.
[tabs] [tabs]
====== ======
@ -325,7 +324,7 @@ constructor before setting the specified value. If the element type does not hav
default constructor, `null` will be added to the array or list. If there is no built-in default constructor, `null` will be added to the array or list. If there is no built-in
or custom converter that knows how to set the value, `null` will remain in the array or or custom converter that knows how to set the value, `null` will remain in the array or
list at the specified index. The following example demonstrates how to automatically grow list at the specified index. The following example demonstrates how to automatically grow
the list: the list.
[tabs] [tabs]
====== ======
@ -394,11 +393,11 @@ xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
[[expressions-spel-compilation]] [[expressions-spel-compilation]]
== SpEL Compilation == SpEL Compilation
Spring Framework 4.1 includes a basic expression compiler. Expressions are usually Spring provides a basic compiler for SpEL expressions. Expressions are usually
interpreted, which provides a lot of dynamic flexibility during evaluation but interpreted, which provides a lot of dynamic flexibility during evaluation but does not
does not provide optimum performance. For occasional expression usage, provide optimum performance. For occasional expression usage, this is fine, but, when
this is fine, but, when used by other components such as Spring Integration, used by other components such as Spring Integration, performance can be very important,
performance can be very important, and there is no real need for the dynamism. and there is no real need for the dynamism.
The SpEL compiler is intended to address this need. During evaluation, the compiler The SpEL compiler is intended to address this need. During evaluation, the compiler
generates a Java class that embodies the expression behavior at runtime and uses that generates a Java class that embodies the expression behavior at runtime and uses that
@ -411,16 +410,17 @@ information can cause trouble later if the types of the various expression eleme
change over time. For this reason, compilation is best suited to expressions whose change over time. For this reason, compilation is best suited to expressions whose
type information is not going to change on repeated evaluations. type information is not going to change on repeated evaluations.
Consider the following basic expression: Consider the following basic expression.
[source,java,indent=0,subs="verbatim,quotes"]
---- ----
someArray[0].someProperty.someOtherProperty < 0.1 someArray[0].someProperty.someOtherProperty < 0.1
---- ----
Because the preceding expression involves array access, some property de-referencing, Because the preceding expression involves array access, some property de-referencing, and
and numeric operations, the performance gain can be very noticeable. In an example numeric operations, the performance gain can be very noticeable. In an example micro
micro benchmark run of 50000 iterations, it took 75ms to evaluate by using the benchmark run of 50,000 iterations, it took 75ms to evaluate by using the interpreter and
interpreter and only 3ms using the compiled version of the expression. only 3ms using the compiled version of the expression.
[[expressions-compiler-configuration]] [[expressions-compiler-configuration]]
@ -428,33 +428,34 @@ interpreter and only 3ms using the compiled version of the expression.
The compiler is not turned on by default, but you can turn it on in either of two The compiler is not turned on by default, but you can turn it on in either of two
different ways. You can turn it on by using the parser configuration process different ways. You can turn it on by using the parser configuration process
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed earlier]) or by using a Spring property (xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed
when SpEL usage is embedded inside another component. This section discusses both of earlier]) or by using a Spring property when SpEL usage is embedded inside another
these options. component. This section discusses both of these options.
The compiler can operate in one of three modes, which are captured in the The compiler can operate in one of three modes, which are captured in the
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows: `org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
* `OFF` (default): The compiler is switched off. * `OFF` (default): The compiler is switched off.
* `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This * `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This
is typically after the first interpreted evaluation. If the compiled expression fails is typically after the first interpreted evaluation. If the compiled expression fails
(typically due to a type changing, as described earlier), the caller of the expression (typically due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception. evaluation receives an exception.
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and compiled * `MIXED`: In mixed mode, the expressions silently switch between interpreted and
mode over time. After some number of interpreted runs, they switch to compiled compiled mode over time. After some number of interpreted runs, they switch to compiled
form and, if something goes wrong with the compiled form (such as a type changing, as form and, if something goes wrong with the compiled form (such as a type changing, as
described earlier), the expression automatically switches back to interpreted form described earlier), the expression automatically switches back to interpreted form
again. Sometime later, it may generate another compiled form and switch to it. Basically, again. Sometime later, it may generate another compiled form and switch to it.
the exception that the user gets in `IMMEDIATE` mode is instead handled internally. Basically, the exception that the user gets in `IMMEDIATE` mode is instead handled
internally.
`IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that `IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that
have side effects. If a compiled expression blows up after partially succeeding, it have side effects. If a compiled expression blows up after partially succeeding, it
may have already done something that has affected the state of the system. If this may have already done something that has affected the state of the system. If this
has happened, the caller may not want it to silently re-run in interpreted mode, has happened, the caller may not want it to silently re-run in interpreted mode,
since part of the expression may be running twice. since part of the expression may be run twice.
After selecting a mode, use the `SpelParserConfiguration` to configure the parser. The After selecting a mode, use the `SpelParserConfiguration` to configure the parser. The
following example shows how to do so: following example shows how to do so.
[tabs] [tabs]
====== ======
@ -491,15 +492,16 @@ Kotlin::
---- ----
====== ======
When you specify the compiler mode, you can also specify a classloader (passing null is allowed). When you specify the compiler mode, you can also specify a `ClassLoader` (passing `null`
Compiled expressions are defined in a child classloader created under any that is supplied. is allowed). Compiled expressions are defined in a child `ClassLoader` created under any
It is important to ensure that, if a classloader is specified, it can see all the types involved in that is supplied. It is important to ensure that, if a `ClassLoader` is specified, it can
the expression evaluation process. If you do not specify a classloader, a default classloader is used see all the types involved in the expression evaluation process. If you do not specify a
(typically the context classloader for the thread that is running during expression evaluation). `ClassLoader`, a default `ClassLoader` is used (typically the context `ClassLoader` for
the thread that is running during expression evaluation).
The second way to configure the compiler is for use when SpEL is embedded inside some The second way to configure the compiler is for use when SpEL is embedded inside some
other component and it may not be possible to configure it through a configuration other component and it may not be possible to configure it through a configuration
object. In these cases, it is possible to set the `spring.expression.compiler.mode` object. In such cases, it is possible to set the `spring.expression.compiler.mode`
property via a JVM system property (or via the property via a JVM system property (or via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to one of the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to one of the
`SpelCompilerMode` enum values (`off`, `immediate`, or `mixed`). `SpelCompilerMode` enum values (`off`, `immediate`, or `mixed`).
@ -508,18 +510,14 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to
[[expressions-compiler-limitations]] [[expressions-compiler-limitations]]
=== Compiler Limitations === Compiler Limitations
Since Spring Framework 4.1, the basic compilation framework is in place. However, the framework Spring does not support compiling every kind of expression. The primary focus is on
does not yet support compiling every kind of expression. The initial focus has been on the common expressions that are likely to be used in performance-critical contexts. The
common expressions that are likely to be used in performance-critical contexts. The following following kinds of expressions cannot be compiled.
kinds of expression cannot be compiled at the moment:
* Expressions involving assignment * Expressions involving assignment
* Expressions relying on the conversion service * Expressions relying on the conversion service
* Expressions using custom resolvers or accessors * Expressions using custom resolvers or accessors
* Expressions using selection or projection * Expressions using selection or projection
More types of expressions will be compilable in the future. Compilation of additional kinds of expressions may be supported in the future.