SimpleEvaluationContext with dedicated factory methods for common cases

Aligned with DataBindingPropertyAccessor and shown in ref doc examples.

Issue: SPR-16588
This commit is contained in:
Juergen Hoeller 2018-03-22 18:09:27 +01:00
parent 025ee83403
commit 51c57d77d9
8 changed files with 154 additions and 152 deletions

View File

@ -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"),

View File

@ -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.
*
* <p>A property can be accessed through a public getter method (when being read)
* <p>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.
*
* <p>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.
* <p>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);

View File

@ -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.
*
* <p>A property can be accessed through a public getter method (when being read)
* <p>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 {
* <p>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);

View File

@ -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.
*
* <p>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.
* <p>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.
*
* <p>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:
* <ul>
* <li>Custom {@code PropertyAccessor} only (no reflection).</li>
* <li>Data binding properties for read-only access.</li>
* <li>Data binding properties for read and write.</li>
* <li>Custom {@code PropertyAccessor} only (no reflection)</li>
* <li>Data binding properties for read-only access</li>
* <li>Data binding properties for read and write</li>
* </ul>
*
* <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.
* <p>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}.
*
* <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 {@code getValue} calls on a pre-compiled
* {@link org.springframework.expression.Expression} with both an
* {@code EvaluationContext} and a root object as arguments
*
* <p>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<PropertyAccessor> propertyAccessors;
private final List<ConstructorResolver> constructorResolvers = Collections.emptyList();
private final List<MethodResolver> 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<PropertyAccessor> 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<ConstructorResolver> getConstructorResolvers() {
return this.constructorResolvers;
return Collections.emptyList();
}
/**
@ -130,7 +134,7 @@ public class SimpleEvaluationContext implements EvaluationContext {
*/
@Override
public List<MethodResolver> 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}.
* <p>Effectively, a shortcut for:
* <pre class="code">
* SimpleEvaluationContext context = SimpleEvaluationContext.builder()
* .dataBindingPropertyAccessor(true)
* .build();
* </pre>
* @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<PropertyAccessor> propertyAccessors = new ArrayList<>();
private final List<PropertyAccessor> propertyAccessors;
@Nullable
private TypeConverter typeConverter;
/**
* Enable access to public properties for data binding purposes.
* <p>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.
* <p>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}.
* <p>By default {@link StandardTypeConverter} is used.
* <p>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}.
* <p>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);
}

View File

@ -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();

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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]