Skip to content

Commit bf5fe46

Browse files
committed
CachedIntrospectionResults completely traverses interface hierarchy
Issue: SPR-16978
1 parent 81cb740 commit bf5fe46

File tree

2 files changed

+40
-25
lines changed

2 files changed

+40
-25
lines changed

spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -297,20 +297,10 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
297297

298298
// Explicitly check implemented interfaces for setter/getter methods as well,
299299
// in particular for Java 8 default methods...
300-
Class<?> clazz = beanClass;
301-
while (clazz != null && clazz != Object.class) {
302-
Class<?>[] ifcs = clazz.getInterfaces();
303-
for (Class<?> ifc : ifcs) {
304-
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
305-
for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
306-
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
307-
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
308-
this.propertyDescriptorCache.put(pd.getName(), pd);
309-
}
310-
}
311-
}
312-
}
313-
clazz = clazz.getSuperclass();
300+
Class<?> currClass = beanClass;
301+
while (currClass != null && currClass != Object.class) {
302+
introspectInterfaces(beanClass, currClass);
303+
currClass = currClass.getSuperclass();
314304
}
315305

316306
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
@@ -320,6 +310,24 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
320310
}
321311
}
322312

313+
private void introspectInterfaces(Class<?> beanClass, Class<?> currClass) throws IntrospectionException {
314+
for (Class<?> ifc : currClass.getInterfaces()) {
315+
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
316+
for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
317+
PropertyDescriptor existingPd = this.propertyDescriptorCache.get(pd.getName());
318+
if (existingPd == null ||
319+
(existingPd.getReadMethod() == null && pd.getReadMethod() != null)) {
320+
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
321+
// against a declared read method, so we prefer read method descriptors here.
322+
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
323+
this.propertyDescriptorCache.put(pd.getName(), pd);
324+
}
325+
}
326+
introspectInterfaces(ifc, ifc);
327+
}
328+
}
329+
}
330+
323331

324332
BeanInfo getBeanInfo() {
325333
return this.beanInfo;

spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 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.
@@ -49,7 +49,8 @@ public void setterDoesNotCallGetter() {
4949
GetterBean target = new GetterBean();
5050
BeanWrapper accessor = createAccessor(target);
5151
accessor.setPropertyValue("name", "tom");
52-
assertTrue("Set name to tom", target.getName().equals("tom"));
52+
assertEquals("tom", target.getAliasedName());
53+
assertEquals("tom", accessor.getPropertyValue("aliasedName"));
5354
}
5455

5556
@Test
@@ -58,15 +59,17 @@ public void getterSilentlyFailWithOldValueExtraction() {
5859
BeanWrapper accessor = createAccessor(target);
5960
accessor.setExtractOldValueForEditor(true); // This will call the getter
6061
accessor.setPropertyValue("name", "tom");
61-
assertTrue("Set name to tom", target.getName().equals("tom"));
62+
assertEquals("tom", target.getAliasedName());
63+
assertEquals("tom", accessor.getPropertyValue("aliasedName"));
6264
}
6365

6466
@Test
6567
public void aliasedSetterThroughDefaultMethod() {
6668
GetterBean target = new GetterBean();
6769
BeanWrapper accessor = createAccessor(target);
6870
accessor.setPropertyValue("aliasedName", "tom");
69-
assertTrue("Set name to tom", target.getAliasedName().equals("tom"));
71+
assertEquals("tom", target.getAliasedName());
72+
assertEquals("tom", accessor.getPropertyValue("aliasedName"));
7073
}
7174

7275
@Test
@@ -216,20 +219,24 @@ public void incompletelyQuotedKeyLeadsToPropertyException() {
216219
}
217220

218221

222+
private interface BaseProperty {
223+
224+
default String getAliasedName() {
225+
return getName();
226+
}
227+
228+
String getName();
229+
}
230+
231+
219232
@SuppressWarnings("unused")
220-
private interface AliasedProperty {
233+
private interface AliasedProperty extends BaseProperty {
221234

222235
default void setAliasedName(String name) {
223236
setName(name);
224237
}
225238

226-
default String getAliasedName() {
227-
return getName();
228-
}
229-
230239
void setName(String name);
231-
232-
String getName();
233240
}
234241

235242

0 commit comments

Comments
 (0)