parent
58011f71e9
commit
cb8c6e3251
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* 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.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;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* 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 = typeName -> {
|
||||
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
|
||||
};
|
||||
|
||||
|
||||
private final List<PropertyAccessor> propertyAccessors;
|
||||
|
||||
private final List<ConstructorResolver> constructorResolvers =
|
||||
Collections.singletonList(new ReflectiveConstructorResolver());
|
||||
|
||||
private final List<MethodResolver> methodResolvers =
|
||||
Collections.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<>();
|
||||
|
||||
|
||||
public SimpleEvaluationContext() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public SimpleEvaluationContext(@Nullable List<PropertyAccessor> accessors, @Nullable TypeConverter converter) {
|
||||
this.propertyAccessors = initPropertyAccessors(accessors);
|
||||
this.typeConverter = converter != null ? converter : new StandardTypeConverter();
|
||||
}
|
||||
|
||||
|
||||
private static List<PropertyAccessor> initPropertyAccessors(@Nullable List<PropertyAccessor> accessors) {
|
||||
if (accessors == null) {
|
||||
accessors = new ArrayList<>(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
|
||||
@Nullable
|
||||
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, @Nullable Object value) {
|
||||
this.variables.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object lookupVariable(String name) {
|
||||
return this.variables.get(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -36,13 +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
|
||||
|
@ -68,7 +62,7 @@ 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
|
||||
|
@ -156,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"]
|
||||
|
@ -172,88 +165,38 @@ 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]]
|
||||
|
@ -280,14 +223,14 @@ 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);
|
||||
|
@ -446,7 +389,7 @@ 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
|
||||
|
@ -455,7 +398,7 @@ 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.
|
||||
|
||||
|
@ -504,7 +447,7 @@ 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.
|
||||
|
@ -659,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
|
||||
|
@ -943,14 +885,14 @@ 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);
|
||||
----
|
||||
|
||||
|
||||
|
@ -1005,16 +947,16 @@ used).
|
|||
=== 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"
|
||||
----
|
||||
|
@ -1037,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 ?{...})
|
||||
|
@ -1052,17 +994,18 @@ an expression are evaluated, #root always refers to the root.
|
|||
=== 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"]
|
||||
|
@ -1079,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(
|
||||
|
@ -1198,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
|
||||
----
|
||||
|
@ -1230,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!!!
|
||||
----
|
||||
|
|
Loading…
Reference in New Issue