diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index eaef7f706e..a0fcaaf050 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -461,7 +461,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA ph.setValue(valueToApply); } catch (TypeMismatchException ex) { - throw ex; + if (!ph.setValueFallbackIfPossible(pv.getValue())) { + throw ex; + } } catch (InvocationTargetException ex) { PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent( @@ -1061,6 +1063,10 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA public abstract Object getValue() throws Exception; public abstract void setValue(@Nullable Object value) throws Exception; + + public boolean setValueFallbackIfPossible(@Nullable Object value) { + return false; + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 330d20c471..0b004fa495 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -19,6 +19,8 @@ package org.springframework.beans; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; @@ -278,6 +280,22 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements ReflectionUtils.makeAccessible(writeMethod); writeMethod.invoke(getWrappedInstance(), value); } + + @Override + public boolean setValueFallbackIfPossible(@Nullable Object value) { + Method writeMethod = this.pd.getWriteMethodFallback(value != null ? value.getClass() : null); + if (writeMethod != null) { + ReflectionUtils.makeAccessible(writeMethod); + try { + writeMethod.invoke(getWrappedInstance(), value); + return true; + } + catch (Exception ex) { + LogFactory.getLog(BeanPropertyHandler.class).debug("Write method fallback failed", ex); + } + } + return false; + } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 7922f0864d..dd11eae7ec 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -54,7 +54,9 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { private final Method writeMethod; @Nullable - private volatile Set ambiguousWriteMethods; + private Set ambiguousWriteMethods; + + private volatile boolean ambiguousWriteMethodsLogged; @Nullable private MethodParameter writeMethodParameter; @@ -147,16 +149,28 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { public Method getWriteMethodForActualAccess() { Assert.state(this.writeMethod != null, "No write method available"); - Set ambiguousCandidates = this.ambiguousWriteMethods; - if (ambiguousCandidates != null) { - this.ambiguousWriteMethods = null; + if (this.ambiguousWriteMethods != null && !this.ambiguousWriteMethodsLogged) { + this.ambiguousWriteMethodsLogged = true; LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).debug("Non-unique JavaBean property '" + getName() + "' being accessed! Ambiguous write methods found next to actually used [" + - this.writeMethod + "]: " + ambiguousCandidates); + this.writeMethod + "]: " + this.ambiguousWriteMethods); } return this.writeMethod; } + @Nullable + public Method getWriteMethodFallback(@Nullable Class valueType) { + if (this.ambiguousWriteMethods != null) { + for (Method method : this.ambiguousWriteMethods) { + Class paramType = method.getParameterTypes()[0]; + if (valueType != null ? paramType.isAssignableFrom(valueType) : !paramType.isPrimitive()) { + return method; + } + } + } + return null; + } + public MethodParameter getWriteMethodParameter() { Assert.state(this.writeMethodParameter != null, "No write method available"); return this.writeMethodParameter; 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 ccaf690a05..e884a778de 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperTests.java @@ -16,6 +16,7 @@ package org.springframework.beans; +import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; @@ -172,10 +173,26 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests { void setterOverload() { SetterOverload target = new SetterOverload(); BeanWrapper accessor = createAccessor(target); + accessor.setPropertyValue("object", "a String"); assertThat(target.value).isEqualTo("a String"); assertThat(target.getObject()).isEqualTo("a String"); assertThat(accessor.getPropertyValue("object")).isEqualTo("a String"); + + accessor.setPropertyValue("object", 1000); + assertThat(target.value).isEqualTo("1000"); + assertThat(target.getObject()).isEqualTo("1000"); + assertThat(accessor.getPropertyValue("object")).isEqualTo("1000"); + + accessor.setPropertyValue("value", 1000); + assertThat(target.value).isEqualTo("1000i"); + assertThat(target.getObject()).isEqualTo("1000i"); + assertThat(accessor.getPropertyValue("object")).isEqualTo("1000i"); + + accessor.setPropertyValue("value", Duration.ofSeconds(1000)); + assertThat(target.value).isEqualTo("1000s"); + assertThat(target.getObject()).isEqualTo("1000s"); + assertThat(accessor.getPropertyValue("object")).isEqualTo("1000s"); } @Test @@ -382,7 +399,7 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests { public String value; public void setObject(Integer length) { - this.value = length.toString(); + this.value = length + "i"; } public void setObject(String object) { @@ -392,6 +409,14 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests { public String getObject() { return this.value; } + + public void setValue(int length) { + this.value = length + "i"; + } + + public void setValue(Duration duration) { + this.value = duration.getSeconds() + "s"; + } }