Skip to content

Commit 4525527

Browse files
philwebbcbeams
authored andcommitted
SpEL support for methods and properties on class …
Update the ReflectiveMethodResolver and ReflectivePropertyAccessor to allow methods and properties of java.lang.Class to be resolved when the target object is a class. Issue: SPR-9017 Backport-Commit: d28592a
1 parent 98075c3 commit 4525527

File tree

4 files changed

+68
-15
lines changed

4 files changed

+68
-15
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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,8 +21,10 @@
2121
import java.util.Arrays;
2222
import java.util.Comparator;
2323
import java.util.HashMap;
24+
import java.util.HashSet;
2425
import java.util.List;
2526
import java.util.Map;
27+
import java.util.Set;
2628

2729
import org.springframework.core.MethodParameter;
2830
import org.springframework.core.convert.TypeDescriptor;
@@ -90,7 +92,7 @@ public MethodExecutor resolve(EvaluationContext context, Object targetObject, St
9092
try {
9193
TypeConverter typeConverter = context.getTypeConverter();
9294
Class<?> type = (targetObject instanceof Class ? (Class<?>) targetObject : targetObject.getClass());
93-
Method[] methods = getMethods(type);
95+
Method[] methods = getMethods(type, targetObject);
9496

9597
// If a filter is registered for this type, call it
9698
MethodFilter filter = (this.filters != null ? this.filters.get(type) : null);
@@ -197,6 +199,16 @@ public void registerMethodFilter(Class<?> type, MethodFilter filter) {
197199
}
198200
}
199201

202+
private Method[] getMethods(Class<?> type, Object targetObject) {
203+
if(targetObject instanceof Class) {
204+
Set<Method> methods = new HashSet<Method>();
205+
methods.addAll(Arrays.asList(getMethods(type)));
206+
methods.addAll(Arrays.asList(getMethods(targetObject.getClass())));
207+
return methods.toArray(new Method[methods.size()]);
208+
}
209+
return getMethods(type);
210+
}
211+
200212
/**
201213
* Return the set of methods for this type. The default implementation returns the
202214
* result of Class#getMethods for the given {@code type}, but subclasses may override

org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -71,7 +71,7 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
7171
if (this.readerCache.containsKey(cacheKey)) {
7272
return true;
7373
}
74-
Method method = findGetterForProperty(name, type, target instanceof Class);
74+
Method method = findGetterForProperty(name, type, target);
7575
if (method != null) {
7676
// Treat it like a property
7777
// The readerCache will only contain gettable properties (let's not worry about setters for now)
@@ -82,10 +82,10 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
8282
return true;
8383
}
8484
else {
85-
Field field = findField(name, type, target instanceof Class);
85+
Field field = findField(name, type, target);
8686
if (field != null) {
8787
TypeDescriptor typeDescriptor = new TypeDescriptor(field);
88-
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
88+
this.readerCache.put(cacheKey, new InvokerPair(field,typeDescriptor));
8989
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
9090
return true;
9191
}
@@ -112,7 +112,7 @@ public TypedValue read(EvaluationContext context, Object target, String name) th
112112
if (invoker == null || invoker.member instanceof Method) {
113113
Method method = (Method) (invoker != null ? invoker.member : null);
114114
if (method == null) {
115-
method = findGetterForProperty(name, type, target instanceof Class);
115+
method = findGetterForProperty(name, type, target);
116116
if (method != null) {
117117
// TODO remove the duplication here between canRead and read
118118
// Treat it like a property
@@ -138,7 +138,7 @@ public TypedValue read(EvaluationContext context, Object target, String name) th
138138
if (invoker == null || invoker.member instanceof Field) {
139139
Field field = (Field) (invoker == null ? null : invoker.member);
140140
if (field == null) {
141-
field = findField(name, type, target instanceof Class);
141+
field = findField(name, type, target);
142142
if (field != null) {
143143
invoker = new InvokerPair(field, new TypeDescriptor(field));
144144
this.readerCache.put(cacheKey, invoker);
@@ -168,7 +168,7 @@ public boolean canWrite(EvaluationContext context, Object target, String name) t
168168
if (this.writerCache.containsKey(cacheKey)) {
169169
return true;
170170
}
171-
Method method = findSetterForProperty(name, type, target instanceof Class);
171+
Method method = findSetterForProperty(name, type, target);
172172
if (method != null) {
173173
// Treat it like a property
174174
Property property = new Property(type, null, method);
@@ -178,7 +178,7 @@ public boolean canWrite(EvaluationContext context, Object target, String name) t
178178
return true;
179179
}
180180
else {
181-
Field field = findField(name, type, target instanceof Class);
181+
Field field = findField(name, type, target);
182182
if (field != null) {
183183
this.writerCache.put(cacheKey, field);
184184
this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
@@ -211,7 +211,7 @@ public void write(EvaluationContext context, Object target, String name, Object
211211
if (cachedMember == null || cachedMember instanceof Method) {
212212
Method method = (Method) cachedMember;
213213
if (method == null) {
214-
method = findSetterForProperty(name, type, target instanceof Class);
214+
method = findSetterForProperty(name, type, target);
215215
if (method != null) {
216216
cachedMember = method;
217217
this.writerCache.put(cacheKey, cachedMember);
@@ -232,7 +232,7 @@ public void write(EvaluationContext context, Object target, String name, Object
232232
if (cachedMember == null || cachedMember instanceof Field) {
233233
Field field = (Field) cachedMember;
234234
if (field == null) {
235-
field = findField(name, type, target instanceof Class);
235+
field = findField(name, type, target);
236236
if (field != null) {
237237
cachedMember = field;
238238
this.writerCache.put(cacheKey, cachedMember);
@@ -281,6 +281,30 @@ else if (canWrite(context, target, name)) {
281281
return typeDescriptor;
282282
}
283283

284+
private Method findGetterForProperty(String propertyName, Class<?> clazz, Object target) {
285+
Method method = findGetterForProperty(propertyName, clazz, target instanceof Class);
286+
if(method == null && target instanceof Class) {
287+
method = findGetterForProperty(propertyName, target.getClass(), false);
288+
}
289+
return method;
290+
}
291+
292+
private Method findSetterForProperty(String propertyName, Class<?> clazz, Object target) {
293+
Method method = findSetterForProperty(propertyName, clazz, target instanceof Class);
294+
if(method == null && target instanceof Class) {
295+
method = findSetterForProperty(propertyName, target.getClass(), false);
296+
}
297+
return method;
298+
}
299+
300+
private Field findField(String name, Class<?> clazz, Object target) {
301+
Field field = findField(name, clazz, target instanceof Class);
302+
if(field == null && target instanceof Class) {
303+
field = findField(name, target.getClass(), false);
304+
}
305+
return field;
306+
}
307+
284308
/**
285309
* Find a getter method for the specified property. A getter is defined as a method whose name start with the prefix
286310
* 'get' and the rest of the name is the same as the property name (with the first character uppercased).
@@ -404,7 +428,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext eContext, Object
404428
if (invocationTarget == null || invocationTarget.member instanceof Method) {
405429
Method method = (Method) (invocationTarget==null?null:invocationTarget.member);
406430
if (method == null) {
407-
method = findGetterForProperty(name, type, target instanceof Class);
431+
method = findGetterForProperty(name, type, target);
408432
if (method != null) {
409433
invocationTarget = new InvokerPair(method,new TypeDescriptor(new MethodParameter(method,-1)));
410434
ReflectionUtils.makeAccessible(method);

org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -38,6 +38,8 @@
3838
import org.springframework.expression.spel.support.StandardEvaluationContext;
3939
import org.springframework.expression.spel.testresources.PlaceOfBirth;
4040

41+
import static org.junit.Assert.*;
42+
4143
/**
4244
* Tests invocation of methods.
4345
*
@@ -361,4 +363,10 @@ public void testInvocationOnNullContextObject() {
361363
evaluateAndCheckError("null.toString()",SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED);
362364
}
363365

366+
@Test
367+
public void testMethodOfClass() throws Exception {
368+
Expression expression = parser.parseExpression("getName()");
369+
Object value = expression.getValue(new StandardEvaluationContext(String.class));
370+
assertEquals(value, "java.lang.String");
371+
}
364372
}

org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -33,6 +33,8 @@
3333
import org.springframework.expression.spel.standard.SpelExpressionParser;
3434
import org.springframework.expression.spel.support.StandardEvaluationContext;
3535

36+
import static org.junit.Assert.*;
37+
3638
///CLOVER:OFF
3739

3840
/**
@@ -155,6 +157,13 @@ public void testAddingRemovingAccessors() {
155157
Assert.assertEquals(2,ctx.getPropertyAccessors().size());
156158
}
157159

160+
@Test
161+
public void testAccessingPropertyOfClass() throws Exception {
162+
Expression expression = parser.parseExpression("name");
163+
Object value = expression.getValue(new StandardEvaluationContext(String.class));
164+
assertEquals(value, "java.lang.String");
165+
}
166+
158167

159168
// This can resolve the property 'flibbles' on any String (very useful...)
160169
private static class StringyPropertyAccessor implements PropertyAccessor {

0 commit comments

Comments
 (0)