diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index f8a723429f..b2fa91ec64 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * 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. @@ -57,13 +57,13 @@ public enum SpelMessage { "Property or field ''{0}'' cannot be found on null"), PROPERTY_OR_FIELD_NOT_READABLE(Kind.ERROR, 1008, - "Property or field ''{0}'' cannot be found on object of type ''{1}'' - maybe not public?"), + "Property or field ''{0}'' cannot be found on object of type ''{1}'' - maybe not public or not valid?"), PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL(Kind.ERROR, 1009, "Property or field ''{0}'' cannot be set on null"), PROPERTY_OR_FIELD_NOT_WRITABLE(Kind.ERROR, 1010, - "Property or field ''{0}'' cannot be set on object of type ''{1}'' - maybe not public?"), + "Property or field ''{0}'' cannot be set on object of type ''{1}'' - maybe not public or not writable?"), METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED(Kind.ERROR, 1011, "Method call: Attempted to call method {0} on null context object"), diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java index 2deb173993..8ee7ec946d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/DataBindingPropertyAccessor.java @@ -22,12 +22,12 @@ import java.lang.reflect.Method; * A {@link org.springframework.expression.PropertyAccessor} variant for data binding * purposes, using reflection to access properties for reading and possibly writing. * - *

A property can be accessed through a public getter method (when being read) + *

A property can be referenced through a public getter method (when being read) * or a public setter method (when being written), and also as a public field. * - *

This accessor is explicitly designed for user-level property evaluation - * and does not resolve technical properties on {@code java.lang.Object}. - * For more resolution power, choose {@link ReflectivePropertyAccessor} instead. + *

This accessor is explicitly designed for user-declared properties and does not + * resolve technical properties on {@code java.lang.Object} or {@code java.lang.Class}. + * For unrestricted resolution, choose {@link ReflectivePropertyAccessor} instead. * * @author Juergen Hoeller * @since 4.3.15 @@ -49,20 +49,21 @@ public class DataBindingPropertyAccessor extends ReflectivePropertyAccessor { } @Override - protected boolean isCandidateForProperty(Method method) { - return (method.getDeclaringClass() != Object.class); + protected boolean isCandidateForProperty(Method method, Class targetClass) { + Class clazz = method.getDeclaringClass(); + return (clazz != Object.class && clazz != Class.class && !ClassLoader.class.isAssignableFrom(targetClass)); } /** - * Create a new data-binding property accessor for read-only access. + * Create a new data-binding property accessor for read-only operations. */ public static DataBindingPropertyAccessor forReadOnlyAccess() { return new DataBindingPropertyAccessor(false); } /** - * Create a new data-binding property accessor for read-write access. + * Create a new data-binding property accessor for read-write operations. */ public static DataBindingPropertyAccessor forReadWriteAccess() { return new DataBindingPropertyAccessor(true); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index c8ad58abdc..41eb0ed7bc 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -48,7 +48,7 @@ import org.springframework.util.StringUtils; * A powerful {@link PropertyAccessor} that uses reflection to access properties * for reading and possibly also for writing. * - *

A property can be accessed through a public getter method (when being read) + *

A property can be referenced through a public getter method (when being read) * or a public setter method (when being written), and also as a public field. * * @author Andy Clement @@ -409,7 +409,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { Method[] methods = getSortedClassMethods(clazz); for (String methodSuffix : methodSuffixes) { for (Method method : methods) { - if (isCandidateForProperty(method) && method.getName().equals(prefix + methodSuffix) && + if (isCandidateForProperty(method, clazz) && method.getName().equals(prefix + methodSuffix) && method.getParameterCount() == numberOfParams && (!mustBeStatic || Modifier.isStatic(method.getModifiers())) && (requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) { @@ -425,9 +425,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { *

The default implementation considers any method as a candidate, even for * non-user-declared properties on the {@link Object} base class. * @param method the Method to evaluate + * @param targetClass the concrete target class that is being introspected * @since 4.3.15 */ - protected boolean isCandidateForProperty(Method method) { + protected boolean isCandidateForProperty(Method method, Class targetClass) { return true; } @@ -518,25 +519,25 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (target == null) { return this; } - Class type = (target instanceof Class ? (Class) target : target.getClass()); - if (type.isArray()) { + Class clazz = (target instanceof Class ? (Class) target : target.getClass()); + if (clazz.isArray()) { return this; } - PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); + PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class); InvokerPair invocationTarget = this.readerCache.get(cacheKey); if (invocationTarget == null || invocationTarget.member instanceof Method) { Method method = (Method) (invocationTarget != null ? invocationTarget.member : null); if (method == null) { - method = findGetterForProperty(name, type, target); + method = findGetterForProperty(name, clazz, target); if (method != null) { invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1))); ReflectionUtils.makeAccessible(method); this.readerCache.put(cacheKey, invocationTarget); } } - if (method != null && isCandidateForProperty(method)) { + if (method != null) { return new OptimalPropertyAccessor(invocationTarget); } } @@ -544,7 +545,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (invocationTarget == null || invocationTarget.member instanceof Field) { Field field = (invocationTarget != null ? (Field) invocationTarget.member : null); if (field == null) { - field = findField(name, type, target instanceof Class); + field = findField(name, clazz, target instanceof Class); if (field != null) { invocationTarget = new InvokerPair(field, new TypeDescriptor(field)); ReflectionUtils.makeAccessible(field); 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 03a543a1ae..20272c18e0 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 @@ -16,13 +16,13 @@ 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; import java.util.Map; +import org.springframework.core.convert.ConversionService; import org.springframework.expression.BeanResolver; import org.springframework.expression.ConstructorResolver; import org.springframework.expression.EvaluationContext; @@ -41,34 +41,42 @@ import org.springframework.lang.Nullable; * A basic implementation of {@link EvaluationContext} that focuses on a subset * of essential SpEL features and configuration options. * - *

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} is tailored to support only a - * subset of the SpEL language syntax, e.g. excluding references to Java types, - * constructors, and bean references. + *

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} 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: + * of support you need to deal with properties and methods in SpEL expressions: *

* - *

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. + *

Conveniently, {@link SimpleEvaluationContext#forReadOnlyDataBinding()} + * enables read access to properties via {@link DataBindingPropertyAccessor}; + * same for {@link SimpleEvaluationContext#forReadWriteDataBinding()} when + * write access is needed as well. Alternatively, configure custom accessors + * via {@link SimpleEvaluationContext#forPropertyAccessors}. + * + *

Note that {@code SimpleEvaluationContext} cannot be configured with + * a default root object. Instead it is meant to be created once and used + * repeatedly through {@code getValue} calls on a pre-compiled + * {@link org.springframework.expression.Expression} with both an + * {@code EvaluationContext} and a root object as arguments + * + *

For more flexibility, consider {@link StandardEvaluationContext} instead. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.3.15 + * @see #forReadOnlyDataBinding() + * @see #forReadWriteDataBinding() * @see StandardEvaluationContext + * @see StandardTypeConverter * @see DataBindingPropertyAccessor */ public class SimpleEvaluationContext implements EvaluationContext { @@ -80,10 +88,6 @@ public class SimpleEvaluationContext implements EvaluationContext { private final List propertyAccessors; - private final List constructorResolvers = Collections.emptyList(); - - private final List methodResolvers = Collections.emptyList(); - private final TypeConverter typeConverter; private final TypeComparator typeComparator = new StandardTypeComparator(); @@ -94,8 +98,8 @@ public class SimpleEvaluationContext implements EvaluationContext { private SimpleEvaluationContext(List accessors, @Nullable TypeConverter converter) { - this.propertyAccessors = Collections.unmodifiableList(new ArrayList<>(accessors)); - this.typeConverter = converter != null ? converter : new StandardTypeConverter(); + this.propertyAccessors = accessors; + this.typeConverter = (converter != null ? converter : new StandardTypeConverter()); } @@ -122,7 +126,7 @@ public class SimpleEvaluationContext implements EvaluationContext { */ @Override public List getConstructorResolvers() { - return this.constructorResolvers; + return Collections.emptyList(); } /** @@ -130,7 +134,7 @@ public class SimpleEvaluationContext implements EvaluationContext { */ @Override public List getMethodResolvers() { - return this.methodResolvers; + return Collections.emptyList(); } /** @@ -170,7 +174,6 @@ public class SimpleEvaluationContext implements EvaluationContext { return this.typeComparator; } - /** * Return an instance of {@link StandardOperatorOverloader}. */ @@ -192,26 +195,31 @@ public class SimpleEvaluationContext implements EvaluationContext { /** - * 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() + * Create a {@code SimpleEvaluationContext} for the specified + * {@link PropertyAccessor} delegates. + * @see ReflectivePropertyAccessor + * @see DataBindingPropertyAccessor */ - public static SimpleEvaluationContext create() { - return new Builder().dataBindingPropertyAccessor(true).build(); + public static Builder forPropertyAccessors(PropertyAccessor... accessors) { + return new Builder(accessors); } /** - * Return a builder to create a {@code SimpleEvaluationContext}. - * @see #create() + * Create a {@code SimpleEvaluationContext} for read-only access to + * public properties via {@link DataBindingPropertyAccessor}. + * @see DataBindingPropertyAccessor#forReadOnlyAccess() */ - public static Builder builder() { - return new Builder(); + public static Builder forReadOnlyDataBinding() { + return new Builder(DataBindingPropertyAccessor.forReadOnlyAccess()); + } + + /** + * Create a {@code SimpleEvaluationContext} for read-write access to + * public properties via {@link DataBindingPropertyAccessor}. + * @see DataBindingPropertyAccessor#forReadOnlyAccess() + */ + public static Builder forReadWriteDataBinding() { + return new Builder(DataBindingPropertyAccessor.forReadWriteAccess()); } @@ -220,43 +228,41 @@ public class SimpleEvaluationContext implements EvaluationContext { */ public static class Builder { - private final List propertyAccessors = new ArrayList<>(); + private final List propertyAccessors; @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; + public Builder(PropertyAccessor... accessors) { + this.propertyAccessors = Arrays.asList(accessors); } /** * Register a custom {@link TypeConverter}. - *

By default {@link StandardTypeConverter} is used. + *

By default a {@link StandardTypeConverter} backed by a + * {@link org.springframework.core.convert.support.DefaultConversionService} + * is used. + * @see #withConversionService + * @see StandardTypeConverter#StandardTypeConverter() */ - public Builder typeConverter(TypeConverter converter) { + public Builder withTypeConverter(TypeConverter converter) { this.typeConverter = converter; return this; } + /** + * Register a custom {@link ConversionService}. + *

By default a {@link StandardTypeConverter} backed by a + * {@link org.springframework.core.convert.support.DefaultConversionService} + * is used. + * @see #withTypeConverter + * @see StandardTypeConverter#StandardTypeConverter(ConversionService) + */ + public Builder withConversionService(ConversionService conversionService) { + this.typeConverter = new StandardTypeConverter(conversionService); + 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/StandardTypeConverter.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index fce9643b65..77ff16342e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * 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. @@ -42,6 +42,7 @@ public class StandardTypeConverter implements TypeConverter { /** * Create a StandardTypeConverter for the default ConversionService. + * @see DefaultConversionService#getSharedInstance() */ public StandardTypeConverter() { this.conversionService = DefaultConversionService.getSharedInstance(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java index e8bf01cd41..a2069ee875 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java @@ -87,7 +87,7 @@ public class PropertyAccessTests extends AbstractExpressionTests { } assertFalse(expr.isWritable(context)); try { - expr.setValue(context,"abc"); + expr.setValue(context, "abc"); fail("Should have failed - default property resolver cannot resolve on null"); } catch (Exception ex) { @@ -95,13 +95,13 @@ public class PropertyAccessTests extends AbstractExpressionTests { } } - private void checkException(Exception e, SpelMessage expectedMessage) { - if (e instanceof SpelEvaluationException) { - SpelMessage sm = ((SpelEvaluationException)e).getMessageCode(); - assertEquals("Expected exception type did not occur",expectedMessage,sm); + private void checkException(Exception ex, SpelMessage expectedMessage) { + if (ex instanceof SpelEvaluationException) { + SpelMessage sm = ((SpelEvaluationException) ex).getMessageCode(); + assertEquals("Expected exception type did not occur", expectedMessage, sm); } else { - fail("Should be a SpelException "+e); + fail("Should be a SpelException " + ex); } } @@ -210,7 +210,12 @@ public class PropertyAccessTests extends AbstractExpressionTests { assertEquals("p2", expr.getValue(context, target)); parser.parseExpression("name='p3'").getValue(context, target); + assertEquals("p3", target.getName()); assertEquals("p3", expr.getValue(context, target)); + + expr.setValue(context, target, "p4"); + assertEquals("p4", target.getName()); + assertEquals("p4", expr.getValue(context, target)); } @Test(expected = SpelEvaluationException.class) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java index ae9c6b7414..18c9a9169e 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java @@ -64,10 +64,9 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { /** Default maximum number of entries for the destination cache: 1024 */ public static final int DEFAULT_CACHE_LIMIT = 1024; - /** Static evaluation context to re-use */ - private static SimpleEvaluationContext evaluationContext = SimpleEvaluationContext.builder() - .propertyAccessor(new SimpMessageHeaderPropertyAccessor()).build(); - + /** Static evaluation context to reuse */ + private static EvaluationContext messageEvalContext = + SimpleEvaluationContext.forPropertyAccessors(new SimpMessageHeaderPropertyAccessor()).build(); private PathMatcher pathMatcher = new AntPathMatcher(); @@ -213,7 +212,7 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry { continue; } try { - if (Boolean.TRUE.equals(expression.getValue(evaluationContext, message, Boolean.class))) { + if (Boolean.TRUE.equals(expression.getValue(messageEvalContext, message, Boolean.class))) { result.add(sessionId, subId); } } diff --git a/src/docs/asciidoc/core/core-expressions.adoc b/src/docs/asciidoc/core/core-expressions.adoc index 8ae51a9688..d2a3c25b82 100644 --- a/src/docs/asciidoc/core/core-expressions.adoc +++ b/src/docs/asciidoc/core/core-expressions.adoc @@ -186,9 +186,8 @@ out-of-the-box 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, property-based filters, and -others. +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. * `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 @@ -201,9 +200,9 @@ By default, the `create()` static factory method enables only read access to pro 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. +. Custom {@code PropertyAccessor} only (no reflection) +. Data binding properties for read-only access +. Data binding properties for read and write [[expressions-type-conversion]] @@ -232,11 +231,10 @@ being placed in it. A simple example: Simple simple = new Simple(); simple.booleanList.add(true); - SimpleEvaluationContext context = SimpleEvaluationContext().create(); + EvaluationContext context = SimpleEvaluationContext().forReadOnlyDataBinding().build(); // 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(context, simple, "false"); // b will be false @@ -609,7 +607,7 @@ arrays and lists are obtained using square bracket notation. [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - SimpleEvaluationContext context = SimpleEvaluationContext.create(); + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); // Inventions Array @@ -851,33 +849,33 @@ operators are demonstrated below. [subs="verbatim,quotes"] ---- // Addition - int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 + int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression( - "'test' + ' ' + 'string'").getValue(String.class); // 'test string' + "'test' + ' ' + 'string'").getValue(String.class); // 'test string' // Subtraction - int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 + int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 - double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 + double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 // Multiplication - int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 + int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 - double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 + double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 // Division - int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 + int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 - double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 + double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 // Modulus - int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 + int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 - int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 + int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 // Operator precedence - int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 + int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 ---- @@ -892,14 +890,13 @@ 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 = SimpleEvaluationContext.create(); + EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); - parser.parseExpression("Name").setValue(context, inventor, "Alexander Seovic2"); + parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic"); // alternatively - String aleks = parser.parseExpression( - "Name = 'Alexandar Seovic'").getValue(context, inventor, String.class); + "Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class); ---- @@ -960,12 +957,12 @@ are set using the method setVariable on `EvaluationContext` implementations. [subs="verbatim,quotes"] ---- Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); - SimpleEvaluationContext context = SimpleEvaluationContext.create(); + + EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); context.setVariable("newName", "Mike Tesla"); parser.parseExpression("Name = #newName").getValue(context, tesla); - - System.out.println(tesla.getName()) // "Mike Tesla" + System.out.println(tesla.getName()) // "Mike Tesla" ---- @@ -986,8 +983,8 @@ 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 = SimpleEvaluationContext.create(); - context.setVariable("primes",primes); + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess(); + context.setVariable("primes", primes); // all prime numbers > 10 from the list (using selection ?{...}) // evaluates to [11, 13, 17] @@ -1008,7 +1005,7 @@ expression string. The function is registered through the `EvaluationContext`. ---- Method method = ...; - SimpleEvaluationContext context = SimpleEvaluationContext.create(); + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("myFunction", method); ---- @@ -1020,7 +1017,7 @@ For example, given a utility method to reverse a string is shown below: public abstract class StringUtils { public static String reverseString(String input) { - StringBuilder backwards = new StringBuilder(); + StringBuilder backwards = new StringBuilder(input.length()); for (int i = 0; i < input.length(); i++) backwards.append(input.charAt(input.length() - 1 - i)); } @@ -1035,13 +1032,13 @@ The above method can then be registered and used as follows: [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - SimpleEvaluationContext context = SimpleEvaluationContext.create(); + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("reverseString", - StringUtils.class.getDeclaredMethod("reverseString", String.class)); + StringUtils.class.getDeclaredMethod("reverseString", String.class)); String helloWorldReversed = parser.parseExpression( - "#reverseString('hello')").getValue(context, String.class); + "#reverseString('hello')").getValue(context, String.class); ---- @@ -1056,7 +1053,7 @@ lookup beans from an expression using the (@) symbol. [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); - StandardEvaluationContext context = StandardEvaluationContext.create(); + StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation @@ -1069,7 +1066,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 = StandardEvaluationContext.create(); + StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new MyBeanResolver()); // This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation @@ -1124,7 +1121,7 @@ example: [subs="verbatim,quotes"] ---- String name = "Elvis Presley"; - String displayName = name != null ? name : "Unknown"; + String displayName = (name != null ? name : "Unknown"); ---- Instead you can use the Elvis operator, named for the resemblance to Elvis' hair style. @@ -1135,8 +1132,7 @@ Instead you can use the Elvis operator, named for the resemblance to Elvis' hair ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(String.class); - - System.out.println(name); // 'Unknown' + System.out.println(name); // 'Unknown' ---- Here is a more complex example. @@ -1145,19 +1141,15 @@ Here is a more complex example. [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); - SimpleEvaluationContext context = SimpleEvaluationContext.create(); - String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); - - System.out.println(name); // Nikola Tesla + System.out.println(name); // Nikola Tesla tesla.setName(null); - name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class); - - System.out.println(name); // Elvis Presley + System.out.println(name); // Elvis Presley ---- @@ -1175,20 +1167,17 @@ safe navigation operator will simply return null instead of throwing an exceptio [subs="verbatim,quotes"] ---- ExpressionParser parser = new SpelExpressionParser(); + EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); - SimpleEvaluationContext context = SimpleEvaluationContext.create(); - String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); - System.out.println(city); // Smiljan + System.out.println(city); // Smiljan tesla.setPlaceOfBirth(null); - city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class); - - System.out.println(city); // null - does not throw NullPointerException!!! + System.out.println(city); // null - does not throw NullPointerException!!! ---- [NOTE]