Introduce write method fallback step for overloaded setter methods

Closes gh-31872
This commit is contained in:
Juergen Hoeller 2023-12-21 14:32:10 +01:00
parent 85cb6cc5fb
commit bd65a19d71
4 changed files with 70 additions and 7 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -54,7 +54,9 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
private final Method writeMethod;
@Nullable
private volatile Set<Method> ambiguousWriteMethods;
private Set<Method> 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<Method> 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;

View File

@ -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";
}
}