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
)
This commit is contained in:
parent
1ea7f741fe
commit
5854d519a1
|
@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.convert.Property;
|
import org.springframework.core.convert.Property;
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
import org.springframework.core.style.ToStringCreator;
|
||||||
import org.springframework.expression.AccessException;
|
import org.springframework.expression.AccessException;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
|
@ -71,7 +72,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
if (type.isArray() && name.equals("length")) {
|
if (type.isArray() && name.equals("length")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
|
||||||
if (this.readerCache.containsKey(cacheKey)) {
|
if (this.readerCache.containsKey(cacheKey)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +111,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
return new TypedValue(Array.getLength(target));
|
return new TypedValue(Array.getLength(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
|
||||||
InvokerPair invoker = this.readerCache.get(cacheKey);
|
InvokerPair invoker = this.readerCache.get(cacheKey);
|
||||||
|
|
||||||
if (invoker == null || invoker.member instanceof Method) {
|
if (invoker == null || invoker.member instanceof Method) {
|
||||||
|
@ -168,7 +169,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
|
||||||
if (this.writerCache.containsKey(cacheKey)) {
|
if (this.writerCache.containsKey(cacheKey)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -209,7 +210,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
throw new AccessException("Type conversion failure",evaluationException);
|
throw new AccessException("Type conversion failure",evaluationException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
|
||||||
Member cachedMember = this.writerCache.get(cacheKey);
|
Member cachedMember = this.writerCache.get(cacheKey);
|
||||||
|
|
||||||
if (cachedMember == null || cachedMember instanceof Method) {
|
if (cachedMember == null || cachedMember instanceof Method) {
|
||||||
|
@ -266,7 +267,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
if (type.isArray() && name.equals("length")) {
|
if (type.isArray() && name.equals("length")) {
|
||||||
return TypeDescriptor.valueOf(Integer.TYPE);
|
return TypeDescriptor.valueOf(Integer.TYPE);
|
||||||
}
|
}
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
|
||||||
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
|
TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
|
||||||
if (typeDescriptor == null) {
|
if (typeDescriptor == null) {
|
||||||
// attempt to populate the cache entry
|
// attempt to populate the cache entry
|
||||||
|
@ -417,7 +418,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheKey cacheKey = new CacheKey(type, name);
|
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
|
||||||
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
|
InvokerPair invocationTarget = this.readerCache.get(cacheKey);
|
||||||
|
|
||||||
if (invocationTarget == null || invocationTarget.member instanceof Method) {
|
if (invocationTarget == null || invocationTarget.member instanceof Method) {
|
||||||
|
@ -476,9 +477,12 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
public CacheKey(Class clazz, String name) {
|
private boolean targetIsClass;
|
||||||
|
|
||||||
|
public CacheKey(Class clazz, String name, boolean targetIsClass) {
|
||||||
this.clazz = clazz;
|
this.clazz = clazz;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.targetIsClass = targetIsClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -490,13 +494,23 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
CacheKey otherKey = (CacheKey) other;
|
CacheKey otherKey = (CacheKey) other;
|
||||||
return (this.clazz.equals(otherKey.clazz) && this.name.equals(otherKey.name));
|
boolean rtn = true;
|
||||||
|
rtn &= this.clazz.equals(otherKey.clazz);
|
||||||
|
rtn &= this.name.equals(otherKey.name);
|
||||||
|
rtn &= this.targetIsClass == otherKey.targetIsClass;
|
||||||
|
return rtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return this.clazz.hashCode() * 29 + this.name.hashCode();
|
return this.clazz.hashCode() * 29 + this.name.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringCreator(this).append("clazz", this.clazz).append("name",
|
||||||
|
this.name).append("targetIsClass", this.targetIsClass).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1818,6 +1818,19 @@ public class SpelReproTests extends ExpressionTestCase {
|
||||||
assertEquals(XYZ.Z, Array.get(result, 2));
|
assertEquals(XYZ.Z, Array.get(result, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void SPR_10486() throws Exception {
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||||
|
SPR10486 rootObject = new SPR10486();
|
||||||
|
Expression classNameExpression = parser.parseExpression("class.name");
|
||||||
|
Expression nameExpression = parser.parseExpression("name");
|
||||||
|
assertThat(classNameExpression.getValue(context, rootObject),
|
||||||
|
equalTo((Object) SPR10486.class.getName()));
|
||||||
|
assertThat(nameExpression.getValue(context, rootObject),
|
||||||
|
equalTo((Object) "name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static enum ABC {A, B, C}
|
private static enum ABC {A, B, C}
|
||||||
|
|
||||||
|
@ -1885,4 +1898,20 @@ public class SpelReproTests extends ExpressionTestCase {
|
||||||
public static class StaticFinalImpl2 extends AbstractStaticFinal {
|
public static class StaticFinalImpl2 extends AbstractStaticFinal {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class TestObject.
|
||||||
|
*/
|
||||||
|
public static class SPR10486 {
|
||||||
|
|
||||||
|
private String name = "name";
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue