diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java
new file mode 100644
index 0000000000..3bcbb6d83e
--- /dev/null
+++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java
@@ -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.
+ *
+ *
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.
+ *
+ *
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 propertyAccessors;
+
+ private final List constructorResolvers =
+ Collections.singletonList(new ReflectiveConstructorResolver());
+
+ private final List methodResolvers =
+ Collections.singletonList(new ReflectiveMethodResolver());
+
+ private final TypeConverter typeConverter;
+
+ private final TypeComparator typeComparator = new StandardTypeComparator();
+
+ private final OperatorOverloader operatorOverloader = new StandardOperatorOverloader();
+
+ private final Map variables = new HashMap<>();
+
+
+ public SimpleEvaluationContext() {
+ this(null, null);
+ }
+
+ public SimpleEvaluationContext(@Nullable List accessors, @Nullable TypeConverter converter) {
+ this.propertyAccessors = initPropertyAccessors(accessors);
+ this.typeConverter = converter != null ? converter : new StandardTypeConverter();
+ }
+
+
+ private static List initPropertyAccessors(@Nullable List 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 getPropertyAccessors() {
+ return this.propertyAccessors;
+ }
+
+ /**
+ * Return a single {@link ReflectiveConstructorResolver}.
+ */
+ @Override
+ public List getConstructorResolvers() {
+ return this.constructorResolvers;
+ }
+
+ /**
+ * Return a single {@link ReflectiveMethodResolver}.
+ */
+ @Override
+ public List 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}.
+ * 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);
+ }
+
+}
diff --git a/src/docs/asciidoc/core/core-expressions.adoc b/src/docs/asciidoc/core/core-expressions.adoc
index 7f0b0acaa6..9c8b125e22 100644
--- a/src/docs/asciidoc/core/core-expressions.adoc
+++ b/src/docs/asciidoc/core/core-expressions.adoc
@@ -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
-<> and <>. 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 `#{ }`.
[[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!!!
----