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 index 3bcbb6d83e..03a543a1ae 100644 --- 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 @@ -17,6 +17,7 @@ package org.springframework.expression.spel.support; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -38,15 +39,26 @@ 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. + * of essential SpEL features and configuration options. * - *

In many cases, the full extent of the SpEL is not + *

In many cases, the full extent of the SpEL language 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. + * that effect, {@code SimpleEvaluationContext} is tailored to support only a + * subset of the SpEL language syntax, e.g. excluding references to Java types, + * constructors, and bean references. + * + *

When creating {@code SimpleEvaluationContext} you need to choose the level + * of support you need to deal with properties and methods in SpEL expressions. + * By default, {@link SimpleEvaluationContext#create()} enables only read access + * to properties via {@link DataBindingPropertyAccessor}. Alternatively, use + * {@link SimpleEvaluationContext#builder()} to configure the exact level of + * support needed, targeting one of, or some combination of the following: + *

* *

Note that {@code SimpleEvaluationContext} cannot be configured with a * default root object. Instead it is meant to be created once and used @@ -56,6 +68,8 @@ import org.springframework.lang.Nullable; * * @author Rossen Stoyanchev * @since 4.3.15 + * @see StandardEvaluationContext + * @see DataBindingPropertyAccessor */ public class SimpleEvaluationContext implements EvaluationContext { @@ -66,11 +80,9 @@ public class SimpleEvaluationContext implements EvaluationContext { private final List propertyAccessors; - private final List constructorResolvers = - Collections.singletonList(new ReflectiveConstructorResolver()); + private final List constructorResolvers = Collections.emptyList(); - private final List methodResolvers = - Collections.singletonList(new ReflectiveMethodResolver()); + private final List methodResolvers = Collections.emptyList(); private final TypeConverter typeConverter; @@ -81,25 +93,12 @@ public class SimpleEvaluationContext implements EvaluationContext { private final Map variables = new HashMap<>(); - public SimpleEvaluationContext() { - this(null, null); - } - - public SimpleEvaluationContext(@Nullable List accessors, @Nullable TypeConverter converter) { - this.propertyAccessors = initPropertyAccessors(accessors); + private SimpleEvaluationContext(List accessors, @Nullable TypeConverter converter) { + this.propertyAccessors = Collections.unmodifiableList(new ArrayList<>(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 @@ -118,7 +117,8 @@ public class SimpleEvaluationContext implements EvaluationContext { } /** - * Return a single {@link ReflectiveConstructorResolver}. + * Return an empty list, always, since this context does not support the + * use of type references. */ @Override public List getConstructorResolvers() { @@ -190,4 +190,76 @@ public class SimpleEvaluationContext implements EvaluationContext { return this.variables.get(name); } + + /** + * Create a {@code SimpleEvaluationContext} with read-only access to + * public properties via {@link DataBindingPropertyAccessor}. + *

Effectively, a shortcut for: + *

+	 * SimpleEvaluationContext context = SimpleEvaluationContext.builder()
+	 *         .dataBindingPropertyAccessor(true)
+	 *         .build();
+	 * 
+ * @see #builder() + */ + public static SimpleEvaluationContext create() { + return new Builder().dataBindingPropertyAccessor(true).build(); + } + + /** + * Return a builder to create a {@code SimpleEvaluationContext}. + * @see #create() + */ + public static Builder builder() { + return new Builder(); + } + + + /** + * Builder for {@code SimpleEvaluationContext}. + */ + public static class Builder { + + private final List propertyAccessors = new ArrayList<>(); + + @Nullable + private TypeConverter typeConverter; + + + /** + * Enable access to public properties for data binding purposes. + *

Effectively, a shortcut for + * {@code propertyAccessor(new DataBindingPropertyAccessor(boolean))}. + * @param readOnlyAccess whether to read-only access to properties, + * {@code "true"}, or read and write, {@code "false"}. + */ + public Builder dataBindingPropertyAccessor(boolean readOnlyAccess) { + return propertyAccessor(readOnlyAccess ? + DataBindingPropertyAccessor.forReadOnlyAccess() : + DataBindingPropertyAccessor.forReadWriteAccess()); + } + + /** + * Register a custom accessor for properties in expressions. + *

By default, the builder does not enable property access. + */ + public Builder propertyAccessor(PropertyAccessor... accessors) { + this.propertyAccessors.addAll(Arrays.asList(accessors)); + return this; + } + + /** + * Register a custom {@link TypeConverter}. + *

By default {@link StandardTypeConverter} is used. + */ + public Builder typeConverter(TypeConverter converter) { + this.typeConverter = converter; + return this; + } + + public SimpleEvaluationContext build() { + return new SimpleEvaluationContext(this.propertyAccessors, this.typeConverter); + } + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java index 421519627e..abfd18313c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java @@ -47,6 +47,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Sam Brannen * @since 3.0 + * @see SimpleEvaluationContext * @see ReflectivePropertyAccessor * @see ReflectiveConstructorResolver * @see ReflectiveMethodResolver diff --git a/src/docs/asciidoc/core/core-expressions.adoc b/src/docs/asciidoc/core/core-expressions.adoc index 9c8b125e22..8ae51a9688 100644 --- a/src/docs/asciidoc/core/core-expressions.adoc +++ b/src/docs/asciidoc/core/core-expressions.adoc @@ -181,23 +181,30 @@ create a boolean condition: === `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 -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. +properties, methods, fields, and to help perform type conversion. There are two +out-of-the-box implementations. -`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 +* `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, 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. +others. -`StandardEvaluationContext` exposes the full set of SpEL language features and +* `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. +`SimpleEvaluationContext` is designed to support only a subset of the SpEL language syntax. +It excludes Java type references, constructors, and bean references. It also requires +explicit choosing the level of support for properties and methods in expressions. +By default, the `create()` static factory method enables only read access to properties. +You can also obtain a builder to configure the exact level of support needed, targeting +one of, or some combination of the following: + +. Custom {@code PropertyAccessor} only (no reflection). +. Data binding properties for read-only access. +. Data binding properties for read and write. + [[expressions-type-conversion]] ==== Type conversion @@ -225,7 +232,7 @@ being placed in it. A simple example: Simple simple = new Simple(); simple.booleanList.add(true); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext().create(); // 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 @@ -602,7 +609,7 @@ arrays and lists are obtained using square bracket notation. [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); // Inventions Array @@ -885,7 +892,7 @@ done within a call to `setValue` but can also be done inside a call to `getValue [subs="verbatim,quotes"] ---- Inventor inventor = new Inventor(); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); parser.parseExpression("Name").setValue(context, inventor, "Alexander Seovic2"); @@ -953,7 +960,7 @@ are set using the method setVariable on `EvaluationContext` implementations. [subs="verbatim,quotes"] ---- Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); context.setVariable("newName", "Mike Tesla"); parser.parseExpression("Name = #newName").getValue(context, tesla); @@ -979,7 +986,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(); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); context.setVariable("primes",primes); // all prime numbers > 10 from the list (using selection ?{...}) @@ -1001,7 +1008,7 @@ expression string. The function is registered through the `EvaluationContext`. ---- Method method = ...; - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); context.setVariable("myFunction", method); ---- @@ -1028,7 +1035,7 @@ The above method can then be registered and used as follows: [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); context.setVariable("reverseString", StringUtils.class.getDeclaredMethod("reverseString", String.class)); @@ -1049,7 +1056,7 @@ lookup beans from an expression using the (@) symbol. [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - StandardEvaluationContext context = new StandardEvaluationContext(); + StandardEvaluationContext context = StandardEvaluationContext.create(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation @@ -1062,7 +1069,7 @@ To access a factory bean itself, the bean name should instead be prefixed with a [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - StandardEvaluationContext context = new StandardEvaluationContext(); + StandardEvaluationContext context = StandardEvaluationContext.create(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation @@ -1140,7 +1147,7 @@ Here is a more complex example. ExpressionParser parser = new SpelExpressionParser(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); @@ -1172,7 +1179,7 @@ safe navigation operator will simply return null instead of throwing an exceptio Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); - SimpleEvaluationContext context = new SimpleEvaluationContext(); + SimpleEvaluationContext context = SimpleEvaluationContext.create(); String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); System.out.println(city); // Smiljan