Add SimpleEvaluationContext

Issue: SPR-16588
This commit is contained in:
Rossen Stoyanchev
2018-03-14 16:10:28 -04:00
parent d57d914b26
commit 4bc3e0c2b4
2 changed files with 294 additions and 115 deletions

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* A basic implementation of {@link EvaluationContext} that focuses on a subset
* of essential SpEL features and configuration options, and relies on default
* strategies otherwise.
*
* <p>In many cases, the full extent of the SpEL is not
* required and should be meaningfully restricted. Examples include but are not
* limited to data binding expressions, property-based filters, and others. To
* that effect, {@code SimpleEvaluationContext} supports only a subset of the
* SpEL language syntax that excludes references to Java types, constructors,
* and bean references.
*
* <p>Note that {@code SimpleEvaluationContext} cannot be configured with a
* default root object. Instead it is meant to be created once and used
* repeatedly through method variants on
* {@link org.springframework.expression.Expression Expression} that accept
* both an {@code EvaluationContext} and a root object.
*
* @author Rossen Stoyanchev
* @since 4.3.15
*/
public class SimpleEvaluationContext implements EvaluationContext {
private static final TypeLocator typeNotFoundTypeLocator = new TypeLocator() {
@Override
public Class<?> findType(String typeName) throws EvaluationException {
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
}
};
private final List<PropertyAccessor> propertyAccessors;
private final List<ConstructorResolver> constructorResolvers =
Collections.<ConstructorResolver>singletonList(new ReflectiveConstructorResolver());
private final List<MethodResolver> methodResolvers =
Collections.<MethodResolver>singletonList(new ReflectiveMethodResolver());
private final TypeConverter typeConverter;
private final TypeComparator typeComparator = new StandardTypeComparator();
private final OperatorOverloader operatorOverloader = new StandardOperatorOverloader();
private final Map<String, Object> variables = new HashMap<String, Object>();
public SimpleEvaluationContext() {
this(null, null);
}
public SimpleEvaluationContext(List<PropertyAccessor> accessors, TypeConverter converter) {
this.propertyAccessors = initPropertyAccessors(accessors);
this.typeConverter = converter != null ? converter : new StandardTypeConverter();
}
private static List<PropertyAccessor> initPropertyAccessors(List<PropertyAccessor> accessors) {
if (accessors == null) {
accessors = new ArrayList<PropertyAccessor>(5);
accessors.add(new ReflectivePropertyAccessor());
}
return accessors;
}
/**
* {@code SimpleEvaluationContext} cannot be configured with a root object.
* It is meant for repeated use with
* {@link org.springframework.expression.Expression Expression} method
* variants that accept both an {@code EvaluationContext} and a root object.
* @return Always returns {@link TypedValue#NULL}.
*/
@Override
public TypedValue getRootObject() {
return TypedValue.NULL;
}
@Override
public List<PropertyAccessor> getPropertyAccessors() {
return this.propertyAccessors;
}
/**
* Return a single {@link ReflectiveConstructorResolver}.
*/
@Override
public List<ConstructorResolver> getConstructorResolvers() {
return this.constructorResolvers;
}
/**
* Return a single {@link ReflectiveMethodResolver}.
*/
@Override
public List<MethodResolver> getMethodResolvers() {
return this.methodResolvers;
}
/**
* {@code SimpleEvaluationContext} does not support use of bean references.
* @return Always returns {@code null}
*/
@Override
public BeanResolver getBeanResolver() {
return null;
}
/**
* {@code SimpleEvaluationContext} does not support use of type references.
* @return {@code TypeLocator} implementation that raises a
* {@link SpelEvaluationException} with {@link SpelMessage#TYPE_NOT_FOUND}.
*/
@Override
public TypeLocator getTypeLocator() {
return typeNotFoundTypeLocator;
}
/**
* The configured {@link TypeConverter}.
* <p>By default this is {@link StandardTypeConverter}.
*/
@Override
public TypeConverter getTypeConverter() {
return this.typeConverter;
}
/**
* Return an instance of {@link StandardTypeComparator}.
*/
@Override
public TypeComparator getTypeComparator() {
return this.typeComparator;
}
/**
* Return an instance of {@link StandardOperatorOverloader}.
*/
@Override
public OperatorOverloader getOperatorOverloader() {
return this.operatorOverloader;
}
@Override
public void setVariable(String name, Object value) {
this.variables.put(name, value);
}
@Override
public Object lookupVariable(String name) {
return this.variables.get(name);
}
}

View File

@@ -6,6 +6,7 @@
[[expressions-intro]]
== Introduction
The Spring Expression Language (SpEL for short) is a powerful expression language that
supports querying and manipulating an object graph at runtime. The language syntax is
similar to Unified EL but offers additional features, most notably method invocation and
@@ -35,12 +36,7 @@ syntax. In several places an Inventor and Inventor's Society class are used as t
target objects for expression evaluation. These class declarations and the data used to
populate them are listed at the end of the chapter.
[[expressions-features]]
== Feature Overview
The expression language supports the following functionality
The expression language supports the following functionality:
* Literal expressions
* Boolean and relational operators
@@ -66,7 +62,8 @@ The expression language supports the following functionality
[[expressions-evaluation]]
== Expression Evaluation using Spring's Expression Interface
== Evaluation
This section introduces the simple use of SpEL interfaces and its expression language.
The complete language reference can be found in the section
<<expressions-language-ref,Language Reference>>.
@@ -153,10 +150,9 @@ result type. An `EvaluationException` will be thrown if the value cannot be cast
type `T` or converted 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). There are two options here
and which to choose depends on whether the object against which the expression is being
evaluated will be changing with each call to evaluate the expression. In the following
example we retrieve the `name` property from an instance of the Inventor class.
against a specific object instance (called the root object). The example shows
how to retrieve the `name` property from an instance of the `Inventor` class or
create a boolean condition:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -169,91 +165,43 @@ example we retrieve the `name` property from an instance of the Inventor class.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("**name**");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);
----
In the last line, the value of the string variable `name` will be set to "Nikola Tesla".
The class StandardEvaluationContext is where you can specify which object the "name"
property will be evaluated against. This is the mechanism to use if the root object is
unlikely to change, it can simply be set once in the evaluation context. If the root
object is likely to change repeatedly, it can be supplied on each call to `getValue`, as
this next example shows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
/ 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**");
String name = (String) exp.getValue(tesla);
----
// name == "Nikola Tesla"
In this case the inventor `tesla` has been supplied directly to `getValue` and the
expression evaluation infrastructure creates and manages a default evaluation context
internally - it did not require one to be supplied.
The StandardEvaluationContext is relatively expensive to construct and during repeated
usage it builds up cached state that enables subsequent expression evaluations to be
performed more quickly. For this reason it is better to cache and reuse them where
possible, rather than construct a new one for each expression evaluation.
In some cases it can be desirable to use a configured evaluation context and yet still
supply a different root object on each call to `getValue`. `getValue` allows both to be
specified on the same call. In these situations the root object passed on the call is
considered to override any (which maybe null) specified on the evaluation context.
[NOTE]
====
In standalone usage of SpEL there is a need to create the parser, parse expressions and
perhaps provide evaluation contexts and a root context object. However, more common
usage is to provide only the SpEL expression string as part of a configuration file, for
example for Spring bean or Spring Web Flow definitions. In this case, the parser,
evaluation context, root object and any predefined variables are all set up implicitly,
requiring the user to specify nothing other than the expressions.
====
As a final introductory example, the use of a boolean operator is shown using the
Inventor object in the previous example.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(context, Boolean.class); // evaluates to true
exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
----
[[expressions-evaluation-context]]
=== The EvaluationContext interface
=== `EvaluationContext`
The interface `EvaluationContext` is used when evaluating an expression to resolve
properties, methods, fields, and to help perform type conversion. The out-of-the-box
implementation, `StandardEvaluationContext`, uses reflection to manipulate the object,
caching `java.lang.reflect.Method`, `java.lang.reflect.Field`, and
`java.lang.reflect.Constructor` instances for increased performance.
implementations, `SimpleEvalutationContext` and `StandardEvaluationContext`, use
reflection to manipulate the object, caching `java.lang.reflect.Method`,
`java.lang.reflect.Field`, and `java.lang.reflect.Constructor` instances for increased
performance.
The `StandardEvaluationContext` is where you may specify the root object to evaluate
against via the method `setRootObject()` or passing the root object into the
constructor. You can also specify variables and functions that will be used in the
expression using the methods `setVariable()` and `registerFunction()`. The use of
variables and functions are described in the language reference sections
<<expressions-ref-variables,Variables>> and <<expressions-ref-functions,Functions>>. The
`StandardEvaluationContext` is also where you can register custom
``ConstructorResolver``s, ``MethodResolver``s, and ``PropertyAccessor``s to extend how SpEL
evaluates expressions. Please refer to the javadoc of these classes for more details.
`SimpleEvaluationContext` exposes a subset of essential SpEL language features and
configuration options. Certain categories of expressions, do not require the full extent
of the SpEL language syntax and arguably should be meaningfully restricted. Examples
include but are not limited to data binding expressions, property-based filters, and
others. To effect, `SimpleEvaluationContext` supports a subset of the SpEL language syntax
that excludes references to Java types, constructors, and bean references.
`StandardEvaluationContext` exposes the full set of SpEL language features and
configuration options. You may use it to specify a default root object, and to configure
every available evaluation-related strategy.
[[expressions-type-conversion]]
==== Type Conversion
==== Type conversion
By default SpEL uses the conversion service available in Spring core (
`org.springframework.core.convert.ConversionService`). This conversion service comes
with many converters built in for common conversions but is also fully extensible so
@@ -275,22 +223,25 @@ being placed in it. A simple example:
}
Simple simple = new Simple();
simple.booleanList.add(true);
StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
SimpleEvaluationContext context = new SimpleEvaluationContext();
// false is passed in here as a string. SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it
parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b will be false
Boolean b = simple.booleanList.get(0);
----
[[expressions-parser-configuration]]
=== Parser configuration
It is possible to configure the SpEL expression parser using a parser configuration object
It is possible to configure the SpEL expression parser 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
indexing into an array or collection and the element at the specified index is `null`
@@ -325,6 +276,8 @@ list it is possible to automatically grow the array or list to accommodate that
It is also possible to configure the behaviour of the SpEL expression compiler.
[[expressions-spel-compilation]]
=== SpEL compilation
@@ -356,6 +309,7 @@ gain can be very noticeable. In an example micro benchmark run of 50000 iteratio
taking 75ms to evaluate using only the interpreter and just 3ms using the compiled version
of the expression.
[[expressions-compiler-configuration]]
==== Compiler configuration
@@ -415,6 +369,7 @@ In these cases it is possible to use a system property. The property
`spring.expression.compiler.mode` can be set to one of the `SpelCompilerMode`
enum values (`off`, `immediate`, or `mixed`).
[[expressions-compiler-limitations]]
==== Compiler limitations
@@ -430,8 +385,12 @@ at the moment:
More and more types of expression will be compilable in the future.
[[expressions-beandef]]
== Expression support for defining bean definitions
== Expressions in bean definitions
SpEL expressions can be used with XML or annotation-based configuration metadata for
defining ``BeanDefinition``s. In both cases the syntax to define the expression is of the
form `#{ <expression string> }`.
@@ -439,7 +398,8 @@ form `#{ <expression string> }`.
[[expressions-beandef-xml-based]]
=== XML based configuration
=== XML configuration
A property or constructor-arg value can be set using expressions as shown below.
[source,xml,indent=0]
@@ -487,7 +447,8 @@ You can also refer to other bean properties by name, for example.
[[expressions-beandef-annotation-based]]
=== Annotation-based configuration
=== Annotation config
The `@Value` annotation can be placed on fields, methods and method/constructor
parameters to specify a default value.
@@ -584,6 +545,7 @@ Autowired methods and constructors can also use the `@Value` annotation.
[[expressions-ref-literal]]
=== Literal expressions
The types of literal expressions supported are strings, numeric values (int, real, hex),
boolean and null. Strings are delimited by single quotes. To put a single quote itself
in a string, use two single quote characters.
@@ -617,6 +579,7 @@ By default real numbers are parsed using Double.parseDouble().
[[expressions-properties-arrays]]
=== Properties, Arrays, Lists, Maps, Indexers
Navigating with property references is easy: just use a period to indicate a nested
property value. The instances of the `Inventor` class, pupin, and tesla, were populated with
data listed in the section <<expressions-example-classes,Classes used in the examples>>.
@@ -639,25 +602,24 @@ arrays and lists are obtained using square bracket notation.
[subs="verbatim,quotes"]
----
ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = new SimpleEvaluationContext();
// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
teslaContext, String.class);
context, tesla, String.class);
// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
societyContext, String.class);
context, ieee, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
societyContext, String.class);
context, ieee, String.class);
----
The contents of maps are obtained by specifying the literal key value within the
@@ -685,6 +647,7 @@ string literals.
[[expressions-inline-lists]]
=== Inline lists
Lists can be expressed directly in an expression using `{}` notation.
[source,java,indent=0]
@@ -700,8 +663,11 @@ Lists can be expressed directly in an expression using `{}` notation.
entirely composed of fixed literals then a constant list is created to represent the
expression, rather than building a new list on each evaluation.
[[expressions-inline-maps]]
=== Inline Maps
Maps can also be expressed directly in an expression using `{key:value}` notation.
[source,java,indent=0]
@@ -717,8 +683,11 @@ of fixed literals or other nested constant structures (lists or maps) then a con
to represent the expression, rather than building a new map on each evaluation. Quoting of the map keys
is optional, the examples above are not using quoted keys.
[[expressions-array-construction]]
=== Array construction
Arrays can be built using the familiar Java syntax, optionally supplying an initializer
to have the array populated at construction time.
@@ -741,6 +710,7 @@ multi-dimensional array.
[[expressions-methods]]
=== Methods
Methods are invoked using typical Java programming syntax. You may also invoke methods
on literals. Varargs are also supported.
@@ -763,6 +733,7 @@ on literals. Varargs are also supported.
[[expressions-operators-relational]]
==== Relational operators
The relational operators; equal, not equal, less than, less than or equal, greater than,
and greater than or equal are supported using standard operator notation.
@@ -825,6 +796,7 @@ shown here: `lt` (`<`), `gt` (`>`), `le` (`<=`), `ge` (`>=`), `eq` (`==`),
[[expressions-operators-logical]]
==== Logical operators
The logical operators that are supported are and, or, and not. Their use is demonstrated
below.
@@ -862,6 +834,7 @@ below.
[[expressions-operators-mathematical]]
==== Mathematical operators
The addition operator can be used on both numbers and strings. Subtraction, multiplication
and division can be used only on numbers. Other mathematical operators supported are
modulus (%) and exponential power (^). Standard operator precedence is enforced. These
@@ -904,6 +877,7 @@ operators are demonstrated below.
[[expressions-assignment]]
=== Assignment
Setting of a property is done by using the assignment operator. This would typically be
done within a call to `setValue` but can also be done inside a call to `getValue`.
@@ -911,20 +885,21 @@ done within a call to `setValue` but can also be done inside a call to `getValue
[subs="verbatim,quotes"]
----
Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
SimpleEvaluationContext context = new SimpleEvaluationContext();
parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
parser.parseExpression("Name").setValue(context, inventor, "Alexander Seovic2");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
"Name = 'Alexandar Seovic'").getValue(context, inventor, String.class);
----
[[expressions-types]]
=== Types
The special `T` operator can be used to specify an instance of java.lang.Class (the
_type_). Static methods are invoked using this operator as well. The
`StandardEvaluationContext` uses a `TypeLocator` to find types and the
@@ -948,6 +923,7 @@ fully qualified, but all other type references must be.
[[expressions-constructors]]
=== Constructors
Constructors can be invoked using the new operator. The fully qualified class name
should be used for all but the primitive type and String (where int, float, etc, can be
used).
@@ -969,17 +945,18 @@ used).
[[expressions-ref-variables]]
=== Variables
Variables can be referenced in the expression using the syntax `#variableName`. Variables
are set using the method setVariable on the `StandardEvaluationContext`.
are set using the method setVariable on `EvaluationContext` implementations.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
SimpleEvaluationContext context = new SimpleEvaluationContext();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context);
parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
----
@@ -987,6 +964,7 @@ are set using the method setVariable on the `StandardEvaluationContext`.
[[expressions-this-root]]
==== The #this and #root variables
The variable #this is always defined and refers to the current evaluation object
(against which unqualified references are resolved). The variable #root is always
defined and refers to the root context object. Although #this may vary as components of
@@ -1001,7 +979,7 @@ an expression are evaluated, #root always refers to the root.
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
SimpleEvaluationContext context = new SimpleEvaluationContext();
context.setVariable("primes",primes);
// all prime numbers > 10 from the list (using selection ?{...})
@@ -1014,18 +992,20 @@ an expression are evaluated, #root always refers to the root.
[[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 with the `StandardEvaluationContext` using
the method.
expression string. The function is registered through the `EvaluationContext`.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
public void registerFunction(String name, Method m)
Method method = ...;
SimpleEvaluationContext context = new SimpleEvaluationContext();
context.setVariable("myFunction", method);
----
A reference to a Java Method provides the implementation of the function. For example, a
utility method to reverse a string is shown below.
For example, given a utility method to reverse a string is shown below:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -1042,16 +1022,15 @@ utility method to reverse a string is shown below.
}
----
This method is then registered with the evaluation context and can be used within an
expression string.
The above method can then be registered and used as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
SimpleEvaluationContext context = new SimpleEvaluationContext();
context.registerFunction("reverseString",
context.setVariable("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
@@ -1062,6 +1041,7 @@ expression string.
[[expressions-bean-references]]
=== Bean references
If the evaluation context has been configured with a bean resolver it is possible to
lookup beans from an expression using the (@) symbol.
@@ -1092,6 +1072,7 @@ To access a factory bean itself, the bean name should instead be prefixed with a
[[expressions-operator-ternary]]
=== Ternary Operator (If-Then-Else)
You can use the ternary operator for performing if-then-else conditional logic inside
the expression. A minimal example is:
@@ -1126,6 +1107,7 @@ ternary operator.
[[expressions-operator-elvis]]
=== The Elvis Operator
The Elvis operator is a shortening of the ternary operator syntax and is used in the
http://www.groovy-lang.org/operators.html#_elvis_operator[Groovy] language.
With the ternary operator syntax you usually have to repeat a variable twice, for
@@ -1158,15 +1140,15 @@ Here is a more complex example.
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
SimpleEvaluationContext context = new SimpleEvaluationContext();
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Elvis Presley
----
@@ -1175,6 +1157,7 @@ Here is a more complex example.
[[expressions-operator-safe-navigation]]
=== Safe Navigation operator
The Safe Navigation operator is used to avoid a `NullPointerException` and comes from
the http://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
language. Typically when you have a reference to an object you might need to verify that
@@ -1189,14 +1172,14 @@ safe navigation operator will simply return null instead of throwing an exceptio
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
SimpleEvaluationContext context = new SimpleEvaluationContext();
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
----
@@ -1219,6 +1202,7 @@ This will inject a system property `pop3.port` if it is defined or 25 if not.
[[expressions-collection-selection]]
=== Collection Selection
Selection is a powerful expression language feature that allows you to transform some
source collection into another by selecting from its entries.