Skip to content

Commit ab15b8e

Browse files
committed
Support overloaded setters when binding beans
Update `JavaBeanBinder` so that overloaded setters can be used when binding. Prior to this commit the setter picked would depend on the order that the JVM returned the declared methods. We now consistently prefer using the setter with a parameter type that matches the getter. Closes gh-16206
1 parent e2dc278 commit ab15b8e

File tree

2 files changed

+110
-22
lines changed

2 files changed

+110
-22
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.
@@ -97,8 +97,10 @@ else if (value == null || !bound.equals(value.get())) {
9797

9898
/**
9999
* The bean being bound.
100+
*
101+
* @param <T> the bean type
100102
*/
101-
private static class Bean<T> {
103+
static class Bean<T> {
102104

103105
private static Bean<?> cached;
104106

@@ -111,23 +113,36 @@ private static class Bean<T> {
111113
Bean(ResolvableType type, Class<?> resolvedType) {
112114
this.type = type;
113115
this.resolvedType = resolvedType;
114-
putProperties(resolvedType);
116+
addProperties(resolvedType);
115117
}
116118

117-
private void putProperties(Class<?> type) {
119+
private void addProperties(Class<?> type) {
118120
while (type != null && !Object.class.equals(type)) {
119-
for (Method method : type.getDeclaredMethods()) {
120-
if (isCandidate(method)) {
121-
addMethod(method);
122-
}
123-
}
124-
for (Field field : type.getDeclaredFields()) {
125-
addField(field);
126-
}
121+
Method[] declaredMethods = type.getDeclaredMethods();
122+
Field[] declaredFields = type.getDeclaredFields();
123+
addProperties(declaredMethods, declaredFields);
127124
type = type.getSuperclass();
128125
}
129126
}
130127

128+
protected void addProperties(Method[] declaredMethods, Field[] declaredFields) {
129+
for (int i = 0; i < declaredMethods.length; i++) {
130+
if (!isCandidate(declaredMethods[i])) {
131+
declaredMethods[i] = null;
132+
}
133+
}
134+
for (Method method : declaredMethods) {
135+
addMethodIfPossible(method, "get", 0, BeanProperty::addGetter);
136+
}
137+
for (Method method : declaredMethods) {
138+
addMethodIfPossible(method, "is", 0, BeanProperty::addGetter);
139+
addMethodIfPossible(method, "set", 1, BeanProperty::addSetter);
140+
}
141+
for (Field field : declaredFields) {
142+
addField(field);
143+
}
144+
}
145+
131146
private boolean isCandidate(Method method) {
132147
int modifiers = method.getModifiers();
133148
return Modifier.isPublic(modifiers) && !Modifier.isAbstract(modifiers)
@@ -136,15 +151,9 @@ private boolean isCandidate(Method method) {
136151
&& !Class.class.equals(method.getDeclaringClass());
137152
}
138153

139-
private void addMethod(Method method) {
140-
addMethodIfPossible(method, "get", 0, BeanProperty::addGetter);
141-
addMethodIfPossible(method, "is", 0, BeanProperty::addGetter);
142-
addMethodIfPossible(method, "set", 1, BeanProperty::addSetter);
143-
}
144-
145154
private void addMethodIfPossible(Method method, String prefix, int parameterCount,
146155
BiConsumer<BeanProperty, Method> consumer) {
147-
if (method.getParameterCount() == parameterCount
156+
if (method != null && method.getParameterCount() == parameterCount
148157
&& method.getName().startsWith(prefix)
149158
&& method.getName().length() > prefix.length()) {
150159
String propertyName = Introspector
@@ -250,7 +259,7 @@ public T get() {
250259
/**
251260
* A bean property being bound.
252261
*/
253-
private static class BeanProperty {
262+
static class BeanProperty {
254263

255264
private final String name;
256265

@@ -274,11 +283,16 @@ public void addGetter(Method getter) {
274283
}
275284

276285
public void addSetter(Method setter) {
277-
if (this.setter == null) {
286+
if (this.setter == null || isBetterSetter(setter)) {
278287
this.setter = setter;
279288
}
280289
}
281290

291+
private boolean isBetterSetter(Method setter) {
292+
return this.getter != null
293+
&& this.getter.getReturnType().equals(setter.getParameterTypes()[0]);
294+
}
295+
282296
public void addField(Field field) {
283297
if (this.field == null) {
284298
this.field = field;

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2018 the original author or authors.
2+
* Copyright 2012-2019 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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.context.properties.bind;
1818

19+
import java.lang.reflect.Field;
20+
import java.lang.reflect.Method;
1921
import java.time.LocalDate;
2022
import java.util.ArrayList;
2123
import java.util.Collection;
@@ -28,11 +30,14 @@
2830
import org.junit.Before;
2931
import org.junit.Test;
3032

33+
import org.springframework.boot.context.properties.bind.JavaBeanBinder.Bean;
34+
import org.springframework.boot.context.properties.bind.JavaBeanBinder.BeanProperty;
3135
import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler;
3236
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
3337
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
3438
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
3539
import org.springframework.boot.convert.Delimiter;
40+
import org.springframework.core.ResolvableType;
3641
import org.springframework.format.annotation.DateTimeFormat;
3742

3843
import static org.assertj.core.api.Assertions.assertThat;
@@ -44,6 +49,7 @@
4449
*
4550
* @author Phillip Webb
4651
* @author Madhura Bhave
52+
* @author Andy Wilkinson
4753
*/
4854
public class JavaBeanBinderTests {
4955

@@ -505,6 +511,56 @@ public void bindToClassShouldCacheWithGenerics() {
505511
assertThat(bean.getBooleans().get("b").getValue()).isEqualTo(true);
506512
}
507513

514+
public void bindToClassWithOverloadedSetterShouldUseSetterThatMatchesField() {
515+
// gh-16206
516+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
517+
source.put("foo.property", "some string");
518+
this.sources.add(source);
519+
PropertyWithOverloadedSetter bean = this.binder
520+
.bind("foo", Bindable.of(PropertyWithOverloadedSetter.class)).get();
521+
assertThat(bean.getProperty()).isEqualTo("some string");
522+
}
523+
524+
@Test
525+
public void beanProperiesPreferMatchingType() {
526+
// gh-16206
527+
528+
ResolvableType type = ResolvableType.forClass(PropertyWithOverloadedSetter.class);
529+
Bean<PropertyWithOverloadedSetter> bean = new Bean<PropertyWithOverloadedSetter>(
530+
type, type.resolve()) {
531+
532+
@Override
533+
protected void addProperties(Method[] declaredMethods,
534+
Field[] declaredFields) {
535+
// We override here because we need a specific order of the declared
536+
// methods and the JVM doesn't give us one
537+
int intSetter = -1;
538+
int stringSetter = -1;
539+
for (int i = 0; i < declaredMethods.length; i++) {
540+
Method method = declaredMethods[i];
541+
if (method.getName().equals("setProperty")) {
542+
if (method.getParameters()[0].getType().equals(int.class)) {
543+
intSetter = i;
544+
}
545+
else {
546+
stringSetter = i;
547+
}
548+
}
549+
}
550+
if (intSetter > stringSetter) {
551+
Method method = declaredMethods[intSetter];
552+
declaredMethods[intSetter] = declaredMethods[stringSetter];
553+
declaredMethods[stringSetter] = method;
554+
}
555+
super.addProperties(declaredMethods, declaredFields);
556+
}
557+
558+
};
559+
BeanProperty property = bean.getProperties().get("property");
560+
PropertyWithOverloadedSetter target = new PropertyWithOverloadedSetter();
561+
property.setValue(() -> target, "some string");
562+
}
563+
508564
public static class ExampleValueBean {
509565

510566
private int intValue;
@@ -955,4 +1011,22 @@ public void setValue(T value) {
9551011

9561012
}
9571013

1014+
public static class PropertyWithOverloadedSetter {
1015+
1016+
private String property;
1017+
1018+
public void setProperty(int property) {
1019+
this.property = String.valueOf(property);
1020+
}
1021+
1022+
public void setProperty(String property) {
1023+
this.property = property;
1024+
}
1025+
1026+
public String getProperty() {
1027+
return this.property;
1028+
}
1029+
1030+
}
1031+
9581032
}

0 commit comments

Comments
 (0)