Skip to content

Commit 65a8aa1

Browse files
committed
Backported DataBindingPropertyAccessor and DataBindingMethodResolver
Issue: SPR-16588
1 parent f046a06 commit 65a8aa1

File tree

8 files changed

+601
-122
lines changed

8 files changed

+601
-122
lines changed

spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,9 +22,9 @@
2222
* Expressions are executed in an evaluation context. It is in this context that
2323
* references are resolved when encountered during expression evaluation.
2424
*
25-
* <p>There is a default implementation of the EvaluationContext,
26-
* {@link org.springframework.expression.spel.support.StandardEvaluationContext} that can
27-
* be extended, rather than having to implement everything.
25+
* <p>There is a default implementation of this EvaluationContext interface:
26+
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
27+
* which can be extended, rather than having to implement everything manually.
2828
*
2929
* @author Andy Clement
3030
* @author Juergen Hoeller
@@ -39,6 +39,11 @@ public interface EvaluationContext {
3939
*/
4040
TypedValue getRootObject();
4141

42+
/**
43+
* Return a list of accessors that will be asked in turn to read/write a property.
44+
*/
45+
List<PropertyAccessor> getPropertyAccessors();
46+
4247
/**
4348
* Return a list of resolvers that will be asked in turn to locate a constructor.
4449
*/
@@ -50,9 +55,9 @@ public interface EvaluationContext {
5055
List<MethodResolver> getMethodResolvers();
5156

5257
/**
53-
* Return a list of accessors that will be asked in turn to read/write a property.
58+
* Return a bean resolver that can look up beans by name.
5459
*/
55-
List<PropertyAccessor> getPropertyAccessors();
60+
BeanResolver getBeanResolver();
5661

5762
/**
5863
* Return a type locator that can be used to find types, either by short or
@@ -76,11 +81,6 @@ public interface EvaluationContext {
7681
*/
7782
OperatorOverloader getOperatorOverloader();
7883

79-
/**
80-
* Return a bean resolver that can look up beans by name.
81-
*/
82-
BeanResolver getBeanResolver();
83-
8484
/**
8585
* Set a named variable within this evaluation context to a specified value.
8686
* @param name variable to set
@@ -91,7 +91,7 @@ public interface EvaluationContext {
9191
/**
9292
* Look up a named variable within this evaluation context.
9393
* @param name variable to lookup
94-
* @return the value of the variable
94+
* @return the value of the variable, or {@code null} if not found
9595
*/
9696
Object lookupVariable(String name);
9797

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.expression.spel.support;
18+
19+
import java.lang.reflect.Method;
20+
import java.lang.reflect.Modifier;
21+
import java.util.List;
22+
23+
import org.springframework.core.convert.TypeDescriptor;
24+
import org.springframework.expression.AccessException;
25+
import org.springframework.expression.EvaluationContext;
26+
import org.springframework.expression.MethodExecutor;
27+
28+
/**
29+
* A {@link org.springframework.expression.MethodResolver} variant for data binding
30+
* purposes, using reflection to access instance methods on a given target object.
31+
*
32+
* <p>This accessor does not resolve static methods and also no technical methods
33+
* on {@code java.lang.Object} or {@code java.lang.Class}.
34+
* For unrestricted resolution, choose {@link ReflectiveMethodResolver} instead.
35+
*
36+
* @author Juergen Hoeller
37+
* @since 4.3.15
38+
* @see #forInstanceMethodInvocation()
39+
* @see DataBindingPropertyAccessor
40+
*/
41+
public class DataBindingMethodResolver extends ReflectiveMethodResolver {
42+
43+
private DataBindingMethodResolver() {
44+
super();
45+
}
46+
47+
@Override
48+
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
49+
List<TypeDescriptor> argumentTypes) throws AccessException {
50+
51+
if (targetObject instanceof Class) {
52+
throw new IllegalArgumentException("DataBindingMethodResolver does not support Class targets");
53+
}
54+
return super.resolve(context, targetObject, name, argumentTypes);
55+
}
56+
57+
@Override
58+
protected boolean isCandidateForInvocation(Method method, Class<?> targetClass) {
59+
if (Modifier.isStatic(method.getModifiers())) {
60+
return false;
61+
}
62+
Class<?> clazz = method.getDeclaringClass();
63+
return (clazz != Object.class && clazz != Class.class && !ClassLoader.class.isAssignableFrom(targetClass));
64+
}
65+
66+
67+
/**
68+
* Create a new data-binding method resolver for instance method resolution.
69+
*/
70+
public static DataBindingMethodResolver forInstanceMethodInvocation() {
71+
return new DataBindingMethodResolver();
72+
}
73+
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.expression.spel.support;
18+
19+
import java.lang.reflect.Method;
20+
21+
/**
22+
* A {@link org.springframework.expression.PropertyAccessor} variant for data binding
23+
* purposes, using reflection to access properties for reading and possibly writing.
24+
*
25+
* <p>A property can be referenced through a public getter method (when being read)
26+
* or a public setter method (when being written), and also as a public field.
27+
*
28+
* <p>This accessor is explicitly designed for user-declared properties and does not
29+
* resolve technical properties on {@code java.lang.Object} or {@code java.lang.Class}.
30+
* For unrestricted resolution, choose {@link ReflectivePropertyAccessor} instead.
31+
*
32+
* @author Juergen Hoeller
33+
* @since 4.3.15
34+
* @see #forReadOnlyAccess()
35+
* @see #forReadWriteAccess()
36+
* @see SimpleEvaluationContext
37+
* @see StandardEvaluationContext
38+
* @see ReflectivePropertyAccessor
39+
*/
40+
public class DataBindingPropertyAccessor extends ReflectivePropertyAccessor {
41+
42+
/**
43+
* Create a new property accessor for reading and possibly also writing.
44+
* @param allowWrite whether to also allow for write operations
45+
* @see #canWrite
46+
*/
47+
private DataBindingPropertyAccessor(boolean allowWrite) {
48+
super(allowWrite);
49+
}
50+
51+
@Override
52+
protected boolean isCandidateForProperty(Method method, Class<?> targetClass) {
53+
Class<?> clazz = method.getDeclaringClass();
54+
return (clazz != Object.class && clazz != Class.class && !ClassLoader.class.isAssignableFrom(targetClass));
55+
}
56+
57+
58+
/**
59+
* Create a new data-binding property accessor for read-only operations.
60+
*/
61+
public static DataBindingPropertyAccessor forReadOnlyAccess() {
62+
return new DataBindingPropertyAccessor(false);
63+
}
64+
65+
/**
66+
* Create a new data-binding property accessor for read-write operations.
67+
*/
68+
public static DataBindingPropertyAccessor forReadWriteAccess() {
69+
return new DataBindingPropertyAccessor(true);
70+
}
71+
72+
}

spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
2121
import java.lang.reflect.Proxy;
2222
import java.util.ArrayList;
2323
import java.util.Arrays;
24-
import java.util.Collection;
2524
import java.util.Collections;
2625
import java.util.Comparator;
2726
import java.util.HashMap;
@@ -81,6 +80,12 @@ public ReflectiveMethodResolver(boolean useDistance) {
8180
}
8281

8382

83+
/**
84+
* Register a filter for methods on the given type.
85+
* @param type the type to filter on
86+
* @param filter the corresponding method filter,
87+
* or {@code null} to clear any filter for the given type
88+
*/
8489
public void registerMethodFilter(Class<?> type, MethodFilter filter) {
8590
if (this.filters == null) {
8691
this.filters = new HashMap<Class<?>, MethodFilter>();
@@ -93,7 +98,6 @@ public void registerMethodFilter(Class<?> type, MethodFilter filter) {
9398
}
9499
}
95100

96-
97101
/**
98102
* Locate a method on a type. There are three kinds of match that might occur:
99103
* <ol>
@@ -219,7 +223,7 @@ else if (matchRequiringConversion != null) {
219223
}
220224
}
221225

222-
private Collection<Method> getMethods(Class<?> type, Object targetObject) {
226+
private Set<Method> getMethods(Class<?> type, Object targetObject) {
223227
if (targetObject instanceof Class) {
224228
Set<Method> result = new LinkedHashSet<Method>();
225229
// Add these so that static methods are invocable on the type: e.g. Float.valueOf(..)
@@ -237,12 +241,24 @@ else if (Proxy.isProxyClass(type)) {
237241
Set<Method> result = new LinkedHashSet<Method>();
238242
// Expose interface methods (not proxy-declared overrides) for proper vararg introspection
239243
for (Class<?> ifc : type.getInterfaces()) {
240-
result.addAll(Arrays.asList(getMethods(ifc)));
244+
Method[] methods = getMethods(ifc);
245+
for (Method method : methods) {
246+
if (isCandidateForInvocation(method, type)) {
247+
result.add(method);
248+
}
249+
}
241250
}
242251
return result;
243252
}
244253
else {
245-
return Arrays.asList(getMethods(type));
254+
Set<Method> result = new LinkedHashSet<Method>();
255+
Method[] methods = getMethods(type);
256+
for (Method method : methods) {
257+
if (isCandidateForInvocation(method, type)) {
258+
result.add(method);
259+
}
260+
}
261+
return result;
246262
}
247263
}
248264

@@ -258,4 +274,17 @@ protected Method[] getMethods(Class<?> type) {
258274
return type.getMethods();
259275
}
260276

277+
/**
278+
* Determine whether the given {@code Method} is a candidate for method resolution
279+
* on an instance of the given target class.
280+
* <p>The default implementation considers any method as a candidate, even for
281+
* static methods sand non-user-declared methods on the {@link Object} base class.
282+
* @param method the Method to evaluate
283+
* @param targetClass the concrete target class that is being introspected
284+
* @since 4.3.15
285+
*/
286+
protected boolean isCandidateForInvocation(Method method, Class<?> targetClass) {
287+
return true;
288+
}
289+
261290
}

0 commit comments

Comments
 (0)