Skip to content

Commit 5854d51

Browse files
author
Phillip Webb
committed
Add targetIsClass to SpEL property cache key
Update the `CacheKey` class used by `ReflectivePropertyAccessor` to include if the target object is class. The prevents an incorrect cache hit from being returned when a property with the same name is read on both an object and its class. For example: #{class.name} #{name} Issue: SPR-10486 (cherry picked from commit 6d882b14)
1 parent 1ea7f74 commit 5854d51

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.core.MethodParameter;
3030
import org.springframework.core.convert.Property;
3131
import org.springframework.core.convert.TypeDescriptor;
32+
import org.springframework.core.style.ToStringCreator;
3233
import org.springframework.expression.AccessException;
3334
import org.springframework.expression.EvaluationContext;
3435
import org.springframework.expression.EvaluationException;
@@ -71,7 +72,7 @@ public boolean canRead(EvaluationContext context, Object target, String name) th
7172
if (type.isArray() && name.equals("length")) {
7273
return true;
7374
}
74-
CacheKey cacheKey = new CacheKey(type, name);
75+
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
7576
if (this.readerCache.containsKey(cacheKey)) {
7677
return true;
7778
}
@@ -110,7 +111,7 @@ public TypedValue read(EvaluationContext context, Object target, String name) th
110111
return new TypedValue(Array.getLength(target));
111112
}
112113

113-
CacheKey cacheKey = new CacheKey(type, name);
114+
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
114115
InvokerPair invoker = this.readerCache.get(cacheKey);
115116

116117
if (invoker == null || invoker.member instanceof Method) {
@@ -168,7 +169,7 @@ public boolean canWrite(EvaluationContext context, Object target, String name) t
168169
return false;
169170
}
170171
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
171-
CacheKey cacheKey = new CacheKey(type, name);
172+
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
172173
if (this.writerCache.containsKey(cacheKey)) {
173174
return true;
174175
}
@@ -209,7 +210,7 @@ public void write(EvaluationContext context, Object target, String name, Object
209210
throw new AccessException("Type conversion failure",evaluationException);
210211
}
211212
}
212-
CacheKey cacheKey = new CacheKey(type, name);
213+
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
213214
Member cachedMember = this.writerCache.get(cacheKey);
214215

215216
if (cachedMember == null || cachedMember instanceof Method) {
@@ -266,7 +267,7 @@ private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object targe
266267
if (type.isArray() && name.equals("length")) {
267268
return TypeDescriptor.valueOf(Integer.TYPE);
268269
}
269-
CacheKey cacheKey = new CacheKey(type, name);
270+
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
270271
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
271272
if (typeDescriptor == null) {
272273
// attempt to populate the cache entry
@@ -417,7 +418,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext eContext, Object
417418
return this;
418419
}
419420

420-
CacheKey cacheKey = new CacheKey(type, name);
421+
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
421422
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
422423

423424
if (invocationTarget == null || invocationTarget.member instanceof Method) {
@@ -476,9 +477,12 @@ private static class CacheKey {
476477

477478
private final String name;
478479

479-
public CacheKey(Class clazz, String name) {
480+
private boolean targetIsClass;
481+
482+
public CacheKey(Class clazz, String name, boolean targetIsClass) {
480483
this.clazz = clazz;
481484
this.name = name;
485+
this.targetIsClass = targetIsClass;
482486
}
483487

484488
@Override
@@ -490,13 +494,23 @@ public boolean equals(Object other) {
490494
return false;
491495
}
492496
CacheKey otherKey = (CacheKey) other;
493-
return (this.clazz.equals(otherKey.clazz) && this.name.equals(otherKey.name));
497+
boolean rtn = true;
498+
rtn &= this.clazz.equals(otherKey.clazz);
499+
rtn &= this.name.equals(otherKey.name);
500+
rtn &= this.targetIsClass == otherKey.targetIsClass;
501+
return rtn;
494502
}
495503

496504
@Override
497505
public int hashCode() {
498506
return this.clazz.hashCode() * 29 + this.name.hashCode();
499507
}
508+
509+
@Override
510+
public String toString() {
511+
return new ToStringCreator(this).append("clazz", this.clazz).append("name",
512+
this.name).append("targetIsClass", this.targetIsClass).toString();
513+
}
500514
}
501515

502516

spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,6 +1818,19 @@ public TypedValue execute(EvaluationContext context, Object target, Object... ar
18181818
assertEquals(XYZ.Z, Array.get(result, 2));
18191819
}
18201820

1821+
@Test
1822+
public void SPR_10486() throws Exception {
1823+
SpelExpressionParser parser = new SpelExpressionParser();
1824+
StandardEvaluationContext context = new StandardEvaluationContext();
1825+
SPR10486 rootObject = new SPR10486();
1826+
Expression classNameExpression = parser.parseExpression("class.name");
1827+
Expression nameExpression = parser.parseExpression("name");
1828+
assertThat(classNameExpression.getValue(context, rootObject),
1829+
equalTo((Object) SPR10486.class.getName()));
1830+
assertThat(nameExpression.getValue(context, rootObject),
1831+
equalTo((Object) "name"));
1832+
}
1833+
18211834

18221835
private static enum ABC {A, B, C}
18231836

@@ -1885,4 +1898,20 @@ public static class StaticFinalImpl1 extends AbstractStaticFinal implements Stat
18851898
public static class StaticFinalImpl2 extends AbstractStaticFinal {
18861899
}
18871900

1901+
/**
1902+
* The Class TestObject.
1903+
*/
1904+
public static class SPR10486 {
1905+
1906+
private String name = "name";
1907+
1908+
public String getName() {
1909+
return name;
1910+
}
1911+
1912+
public void setName(String name) {
1913+
this.name = name;
1914+
}
1915+
1916+
}
18881917
}

0 commit comments

Comments
 (0)