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:
parent
025ee83403
commit
51c57d77d9
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue