Evaluation
This section introduces programmatic use of SpEL’s interfaces and its expression language. The complete language reference can be found in the Language Reference.
The following code demonstrates how to use the SpEL API to evaluate the literal string
expression, Hello World
.
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 | The value of the message variable is "Hello World" . |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
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
org.springframework.expression
package and its sub-packages, such as spel.support
.
The ExpressionParser
interface is responsible for parsing an expression string. In the
preceding example, the expression string is a string literal denoted by the surrounding
single quotation marks. The Expression
interface is responsible for evaluating the
defined expression string. The two types of exceptions that can be thrown when calling
parser.parseExpression(…)
and exp.getValue(…)
are ParseException
and
EvaluationException
, respectively.
SpEL supports a wide range of features such as calling methods, accessing properties, and calling constructors.
In the following method invocation example, we call the concat
method on the string
literal, Hello World
.
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 | The value of message is now "Hello World!" . |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | The value of message is now "Hello World!" . |
The following example demonstrates how to access the Bytes
JavaBean property of the
string literal, Hello World
.
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 | This line converts the literal to a byte array. |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | This line converts the literal to a byte array. |
SpEL also supports nested properties by using the standard dot notation (such as
prop1.prop2.prop3
) as well as the corresponding setting of property values.
Public fields may also be accessed.
The following example shows how to use dot notation to get the length of a string literal.
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1 | 'Hello World'.bytes.length gives the length of the literal. |
val parser = SpelExpressionParser()
// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1 | 'Hello World'.bytes.length gives the length of the literal. |
The String’s constructor can be called instead of using a string literal, as the following example shows.
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 | Construct a new String from the literal and convert it to upper case. |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
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)
.
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
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
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 and how to
reference the name
property in a boolean expression.
-
Java
-
Kotlin
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
Understanding EvaluationContext
The EvaluationContext
API is used when evaluating an expression to resolve properties,
methods, or fields and to help perform type conversion. Spring provides two
implementations.
SimpleEvaluationContext
-
Exposes a subset of essential SpEL language features and configuration options, for categories of expressions that do not require the full extent of the SpEL language syntax and should be meaningfully restricted. Examples include but are not limited to data binding expressions and property-based filters.
StandardEvaluationContext
-
Exposes the full set of SpEL language features and configuration options. You can use it to specify a default root object and to configure every available evaluation-related strategy.
SimpleEvaluationContext
is designed to support only a subset of the SpEL language
syntax. For example, it excludes Java type references, constructors, and bean references.
It also requires you to explicitly choose the level of support for properties and methods
in expressions. When creating a SimpleEvaluationContext
you need to choose the level of
support that you need for data binding in SpEL expressions:
-
Data binding for read-only access
-
Data binding for read and write access
-
A custom
PropertyAccessor
(typically not reflection-based), potentially combined with aDataBindingPropertyAccessor
Conveniently, SimpleEvaluationContext.forReadOnlyDataBinding()
enables read-only access
to properties via DataBindingPropertyAccessor
. Similarly,
SimpleEvaluationContext.forReadWriteDataBinding()
enables read and write access to
properties. Alternatively, configure custom accessors via
SimpleEvaluationContext.forPropertyAccessors(…)
, potentially disable assignment, and
optionally activate method resolution and/or a type converter through the builder.
Type Conversion
By default, SpEL uses the conversion service available in Spring core
(org.springframework.core.convert.ConversionService
). This conversion service comes
with many built-in converters for common conversions, but is also fully extensible so
that you can add custom conversions between types. Additionally, it is generics-aware.
This means that, when you work with generic types in expressions, SpEL attempts
conversions to maintain type correctness for any objects it encounters.
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
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.
-
Java
-
Kotlin
class Simple {
public List<Boolean> booleanList = new ArrayList<>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b is false
Boolean b = simple.booleanList.get(0);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")
// b is false
val b = simple.booleanList[0]
Parser Configuration
It is possible to configure the SpEL expression parser by using a parser configuration
object (org.springframework.expression.spel.SpelParserConfiguration
). The configuration
object controls the behavior of some of the expression components. For example, if you
index into a collection and the element at the specified index is null
, SpEL can
automatically create the element. This is useful when using expressions made up of a
chain of property references. Similarly, if you index into a collection and specify an
index that is greater than the current size of the collection, SpEL can automatically
grow the collection to accommodate that index. In order to add an element at the
specified index, SpEL will try to create the element using the element type’s default
constructor before setting the specified value. If the element type does not have a
default constructor, null
will be added to the collection. If there is no built-in
converter or custom converter that knows how to set the value, null
will remain in the
collection at the specified index. The following example demonstrates how to
automatically grow a List
.
-
Java
-
Kotlin
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
class Demo {
var list: List<String>? = null
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3]")
val demo = Demo()
val o = expression.getValue(demo)
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
maxExpressionLength
is configurable. If you create a SpelExpressionParser
programmatically, you can specify a custom maxExpressionLength
when creating the
SpelParserConfiguration
that you provide to the SpelExpressionParser
. If you wish to
set the maxExpressionLength
used for parsing SpEL expressions within an
ApplicationContext
— for example, in XML bean definitions, @Value
, etc. — you can
set a JVM system property or Spring property named spring.context.expression.maxLength
to the maximum expression length needed by your application (see
Supported Spring Properties).
SpEL Compilation
Spring provides a basic compiler for SpEL expressions. Expressions are usually interpreted, which provides a lot of dynamic flexibility during evaluation but does not provide optimum performance. For occasional expression usage, this is fine, but, when used by other components such as Spring Integration, performance can be very important, and there is no real need for the dynamism.
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 class to achieve much faster expression evaluation. Due to the lack of typing around expressions, the compiler uses information gathered during the interpreted evaluations of an expression when performing compilation. For example, it does not know the type of a property reference purely from the expression, but during the first interpreted evaluation, it finds out what it is. Of course, basing compilation on such derived information can cause trouble later if the types of the various expression elements change over time. For this reason, compilation is best suited to expressions whose type information is not going to change on repeated evaluations.
Consider the following basic expression.
someArray[0].someProperty.someOtherProperty < 0.1
Because the preceding expression involves array access, some property de-referencing, and numeric operations, the performance gain can be very noticeable. In an example micro benchmark run of 50,000 iterations, it took 75ms to evaluate by using the interpreter and only 3ms using the compiled version of the expression.
Compiler Configuration
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 (discussed earlier) or by using a Spring property when SpEL usage is embedded inside another component. This section discusses both of these options.
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.
OFF
-
The compiler is switched off, and all expressions will be evaluated in interpreted mode. This is the default mode.
IMMEDIATE
-
In immediate mode, expressions are compiled as soon as possible, typically after the first interpreted evaluation. If evaluation of the compiled expression fails (for example, due to a type changing, as described earlier), the caller of the expression evaluation receives an exception. If the types of various expression elements change over time, consider switching to
MIXED
mode or turning off the compiler. MIXED
-
In mixed mode, expression evaluation silently switches between interpreted and compiled over time. After some number of successful interpreted runs, the expression gets compiled. If evaluation of the compiled expression fails (for example, due to a type changing), that failure will be caught internally, and the system will switch back to interpreted mode for the given expression. Basically, the exception that the caller receives in
IMMEDIATE
mode is instead handled internally. Sometime later, the compiler may generate another compiled form and switch to it. This cycle of switching between interpreted and compiled mode will continue until the system determines that it does not make sense to continue trying — for example, when a certain failure threshold has been reached — at which point the system will permanently switch to interpreted mode for the given expression.
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
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,
since part of the expression may be run twice.
After selecting a mode, use the SpelParserConfiguration
to configure the parser. The
following example shows how to do so.
-
Java
-
Kotlin
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
When you specify the compiler mode, you can also specify a ClassLoader
(passing null
is allowed). Compiled expressions are defined in a child ClassLoader
created under any
that is supplied. It is important to ensure that, if a ClassLoader
is specified, it can
see all the types involved in the expression evaluation process. If you do not specify a
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
other component and it may not be possible to configure it through a configuration
object. In such cases, it is possible to set the spring.expression.compiler.mode
property via a JVM system property (or via the
SpringProperties
mechanism) to one of the
SpelCompilerMode
enum values (off
, immediate
, or mixed
).
Compiler Limitations
Spring does not support compiling every kind of expression. The primary focus is on common expressions that are likely to be used in performance-critical contexts. The following kinds of expressions cannot be compiled.
-
Expressions involving assignment
-
Expressions relying on the conversion service
-
Expressions using custom resolvers
-
Expressions using overloaded operators
-
Expressions using array construction syntax
-
Expressions using selection or projection
-
Expressions using bean references
Compilation of additional kinds of expressions may be supported in the future.