diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index e327916ff50..804ff29440e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -297,20 +297,10 @@ public final class CachedIntrospectionResults { // Explicitly check implemented interfaces for setter/getter methods as well, // in particular for Java 8 default methods... - Class clazz = beanClass; - while (clazz != null && clazz != Object.class) { - Class[] ifcs = clazz.getInterfaces(); - for (Class ifc : ifcs) { - if (!ClassUtils.isJavaLanguageInterface(ifc)) { - for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) { - if (!this.propertyDescriptorCache.containsKey(pd.getName())) { - pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); - this.propertyDescriptorCache.put(pd.getName(), pd); - } - } - } - } - clazz = clazz.getSuperclass(); + Class currClass = beanClass; + while (currClass != null && currClass != Object.class) { + introspectInterfaces(beanClass, currClass); + currClass = currClass.getSuperclass(); } this.typeDescriptorCache = new ConcurrentReferenceHashMap<>(); @@ -320,6 +310,24 @@ public final class CachedIntrospectionResults { } } + private void introspectInterfaces(Class beanClass, Class currClass) throws IntrospectionException { + for (Class ifc : currClass.getInterfaces()) { + if (!ClassUtils.isJavaLanguageInterface(ifc)) { + for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) { + PropertyDescriptor existingPd = this.propertyDescriptorCache.get(pd.getName()); + if (existingPd == null || + (existingPd.getReadMethod() == null && pd.getReadMethod() != null)) { + // GenericTypeAwarePropertyDescriptor leniently resolves a set* write method + // against a declared read method, so we prefer read method descriptors here. + pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); + this.propertyDescriptorCache.put(pd.getName(), pd); + } + } + introspectInterfaces(ifc, ifc); + } + } + } + BeanInfo getBeanInfo() { return this.beanInfo; diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java index 89b2922ef4f..c4251bbc788 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,8 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests { GetterBean target = new GetterBean(); BeanWrapper accessor = createAccessor(target); accessor.setPropertyValue("name", "tom"); - assertTrue("Set name to tom", target.getName().equals("tom")); + assertEquals("tom", target.getAliasedName()); + assertEquals("tom", accessor.getPropertyValue("aliasedName")); } @Test @@ -58,7 +59,8 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests { BeanWrapper accessor = createAccessor(target); accessor.setExtractOldValueForEditor(true); // This will call the getter accessor.setPropertyValue("name", "tom"); - assertTrue("Set name to tom", target.getName().equals("tom")); + assertEquals("tom", target.getAliasedName()); + assertEquals("tom", accessor.getPropertyValue("aliasedName")); } @Test @@ -66,7 +68,8 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests { GetterBean target = new GetterBean(); BeanWrapper accessor = createAccessor(target); accessor.setPropertyValue("aliasedName", "tom"); - assertTrue("Set name to tom", target.getAliasedName().equals("tom")); + assertEquals("tom", target.getAliasedName()); + assertEquals("tom", accessor.getPropertyValue("aliasedName")); } @Test @@ -216,20 +219,24 @@ public class BeanWrapperTests extends AbstractPropertyAccessorTests { } + private interface BaseProperty { + + default String getAliasedName() { + return getName(); + } + + String getName(); + } + + @SuppressWarnings("unused") - private interface AliasedProperty { + private interface AliasedProperty extends BaseProperty { default void setAliasedName(String name) { setName(name); } - default String getAliasedName() { - return getName(); - } - void setName(String name); - - String getName(); }