Skip to content

Commit bd65a19

Browse files
committed
Introduce write method fallback step for overloaded setter methods
Closes gh-31872
1 parent 85cb6cc commit bd65a19

File tree

4 files changed

+70
-7
lines changed

4 files changed

+70
-7
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,9 @@ private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv)
461461
ph.setValue(valueToApply);
462462
}
463463
catch (TypeMismatchException ex) {
464-
throw ex;
464+
if (!ph.setValueFallbackIfPossible(pv.getValue())) {
465+
throw ex;
466+
}
465467
}
466468
catch (InvocationTargetException ex) {
467469
PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
@@ -1061,6 +1063,10 @@ public TypeDescriptor getCollectionType(int nestingLevel) {
10611063
public abstract Object getValue() throws Exception;
10621064

10631065
public abstract void setValue(@Nullable Object value) throws Exception;
1066+
1067+
public boolean setValueFallbackIfPossible(@Nullable Object value) {
1068+
return false;
1069+
}
10641070
}
10651071

10661072

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.beans.PropertyDescriptor;
2020
import java.lang.reflect.Method;
2121

22+
import org.apache.commons.logging.LogFactory;
23+
2224
import org.springframework.core.ResolvableType;
2325
import org.springframework.core.convert.TypeDescriptor;
2426
import org.springframework.lang.Nullable;
@@ -278,6 +280,22 @@ public void setValue(@Nullable Object value) throws Exception {
278280
ReflectionUtils.makeAccessible(writeMethod);
279281
writeMethod.invoke(getWrappedInstance(), value);
280282
}
283+
284+
@Override
285+
public boolean setValueFallbackIfPossible(@Nullable Object value) {
286+
Method writeMethod = this.pd.getWriteMethodFallback(value != null ? value.getClass() : null);
287+
if (writeMethod != null) {
288+
ReflectionUtils.makeAccessible(writeMethod);
289+
try {
290+
writeMethod.invoke(getWrappedInstance(), value);
291+
return true;
292+
}
293+
catch (Exception ex) {
294+
LogFactory.getLog(BeanPropertyHandler.class).debug("Write method fallback failed", ex);
295+
}
296+
}
297+
return false;
298+
}
281299
}
282300

283301
}

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
5454
private final Method writeMethod;
5555

5656
@Nullable
57-
private volatile Set<Method> ambiguousWriteMethods;
57+
private Set<Method> ambiguousWriteMethods;
58+
59+
private volatile boolean ambiguousWriteMethodsLogged;
5860

5961
@Nullable
6062
private MethodParameter writeMethodParameter;
@@ -147,16 +149,28 @@ public Method getWriteMethod() {
147149

148150
public Method getWriteMethodForActualAccess() {
149151
Assert.state(this.writeMethod != null, "No write method available");
150-
Set<Method> ambiguousCandidates = this.ambiguousWriteMethods;
151-
if (ambiguousCandidates != null) {
152-
this.ambiguousWriteMethods = null;
152+
if (this.ambiguousWriteMethods != null && !this.ambiguousWriteMethodsLogged) {
153+
this.ambiguousWriteMethodsLogged = true;
153154
LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).debug("Non-unique JavaBean property '" +
154155
getName() + "' being accessed! Ambiguous write methods found next to actually used [" +
155-
this.writeMethod + "]: " + ambiguousCandidates);
156+
this.writeMethod + "]: " + this.ambiguousWriteMethods);
156157
}
157158
return this.writeMethod;
158159
}
159160

161+
@Nullable
162+
public Method getWriteMethodFallback(@Nullable Class<?> valueType) {
163+
if (this.ambiguousWriteMethods != null) {
164+
for (Method method : this.ambiguousWriteMethods) {
165+
Class<?> paramType = method.getParameterTypes()[0];
166+
if (valueType != null ? paramType.isAssignableFrom(valueType) : !paramType.isPrimitive()) {
167+
return method;
168+
}
169+
}
170+
}
171+
return null;
172+
}
173+
160174
public MethodParameter getWriteMethodParameter() {
161175
Assert.state(this.writeMethodParameter != null, "No write method available");
162176
return this.writeMethodParameter;

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.beans;
1818

19+
import java.time.Duration;
1920
import java.util.Collections;
2021
import java.util.Map;
2122
import java.util.Optional;
@@ -172,10 +173,26 @@ void setPropertyTypeMismatch() {
172173
void setterOverload() {
173174
SetterOverload target = new SetterOverload();
174175
BeanWrapper accessor = createAccessor(target);
176+
175177
accessor.setPropertyValue("object", "a String");
176178
assertThat(target.value).isEqualTo("a String");
177179
assertThat(target.getObject()).isEqualTo("a String");
178180
assertThat(accessor.getPropertyValue("object")).isEqualTo("a String");
181+
182+
accessor.setPropertyValue("object", 1000);
183+
assertThat(target.value).isEqualTo("1000");
184+
assertThat(target.getObject()).isEqualTo("1000");
185+
assertThat(accessor.getPropertyValue("object")).isEqualTo("1000");
186+
187+
accessor.setPropertyValue("value", 1000);
188+
assertThat(target.value).isEqualTo("1000i");
189+
assertThat(target.getObject()).isEqualTo("1000i");
190+
assertThat(accessor.getPropertyValue("object")).isEqualTo("1000i");
191+
192+
accessor.setPropertyValue("value", Duration.ofSeconds(1000));
193+
assertThat(target.value).isEqualTo("1000s");
194+
assertThat(target.getObject()).isEqualTo("1000s");
195+
assertThat(accessor.getPropertyValue("object")).isEqualTo("1000s");
179196
}
180197

181198
@Test
@@ -382,7 +399,7 @@ public static class SetterOverload {
382399
public String value;
383400

384401
public void setObject(Integer length) {
385-
this.value = length.toString();
402+
this.value = length + "i";
386403
}
387404

388405
public void setObject(String object) {
@@ -392,6 +409,14 @@ public void setObject(String object) {
392409
public String getObject() {
393410
return this.value;
394411
}
412+
413+
public void setValue(int length) {
414+
this.value = length + "i";
415+
}
416+
417+
public void setValue(Duration duration) {
418+
this.value = duration.getSeconds() + "s";
419+
}
395420
}
396421

397422

0 commit comments

Comments
 (0)