Skip to content

Commit c8dfec4

Browse files
committed
Merge branch '2.2.x'
Closes spring-projectsgh-19011
2 parents d0c8550 + b6ff0b7 commit c8dfec4

10 files changed

+97
-14
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ public enum BindMethod {
308308
VALUE_OBJECT;
309309

310310
static BindMethod forType(Class<?> type) {
311-
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type) != null)
311+
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type, false) != null)
312312
? VALUE_OBJECT : JAVA_BEAN;
313313
}
314314

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
3737
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
3838

3939
@Override
40-
public Constructor<?> getBindConstructor(Bindable<?> bindable) {
41-
return getBindConstructor(bindable.getType().resolve());
40+
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
41+
return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding);
4242
}
4343

44-
Constructor<?> getBindConstructor(Class<?> type) {
44+
Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
4545
if (type == null) {
4646
return null;
4747
}
4848
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
49-
if (constructor == null && isConstructorBindingAnnotatedType(type)) {
49+
if (constructor == null && (isConstructorBindingAnnotatedType(type) || isNestedConstructorBinding)) {
5050
constructor = deduceBindConstructor(type);
5151
}
5252
return constructor;

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ public interface BindConstructorProvider {
3737
* Return the bind constructor to use for the given bindable, or {@code null} if
3838
* constructor binding is not supported.
3939
* @param bindable the bindable to check
40+
* @param isNestedConstructorBinding if this binding is nested within a constructor
41+
* binding
4042
* @return the bind constructor or {@code null}
4143
*/
42-
Constructor<?> getBindConstructor(Bindable<?> bindable);
44+
Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding);
4345

4446
}

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

+14
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,8 @@ final class Context implements BindContext {
522522

523523
private final Deque<Class<?>> dataObjectBindings = new ArrayDeque<>();
524524

525+
private final Deque<Class<?>> constructorBindings = new ArrayDeque<>();
526+
525527
private ConfigurationProperty configurationProperty;
526528

527529
Context() {
@@ -582,6 +584,18 @@ void clearConfigurationProperty() {
582584
this.configurationProperty = null;
583585
}
584586

587+
void pushConstructorBoundTypes(Class<?> value) {
588+
this.constructorBindings.push(value);
589+
}
590+
591+
boolean isNestedConstructorBinding() {
592+
return !this.constructorBindings.isEmpty();
593+
}
594+
595+
void popConstructorBoundTypes() {
596+
this.constructorBindings.pop();
597+
}
598+
585599
PlaceholdersResolver getPlaceholdersResolver() {
586600
return Binder.this.placeholdersResolver;
587601
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
class DefaultBindConstructorProvider implements BindConstructorProvider {
3131

3232
@Override
33-
public Constructor<?> getBindConstructor(Bindable<?> bindable) {
33+
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
3434
Class<?> type = bindable.getType().resolve();
3535
if (bindable.getValue() != null || type == null) {
3636
return null;

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ class ValueObjectBinder implements DataObjectBinder {
5353
@Override
5454
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Context context,
5555
DataObjectPropertyBinder propertyBinder) {
56-
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider);
56+
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider, context);
5757
if (valueObject == null) {
5858
return null;
5959
}
60+
context.pushConstructorBoundTypes(target.getType().resolve());
6061
List<ConstructorParameter> parameters = valueObject.getConstructorParameters();
6162
List<Object> args = new ArrayList<>(parameters.size());
6263
boolean bound = false;
@@ -67,12 +68,13 @@ public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Con
6768
args.add(arg);
6869
}
6970
context.clearConfigurationProperty();
71+
context.popConstructorBoundTypes();
7072
return bound ? valueObject.instantiate(args) : null;
7173
}
7274

7375
@Override
7476
public <T> T create(Bindable<T> target, Binder.Context context) {
75-
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider);
77+
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider, context);
7678
if (valueObject == null) {
7779
return null;
7880
}
@@ -104,12 +106,14 @@ T instantiate(List<Object> args) {
104106
abstract List<ConstructorParameter> getConstructorParameters();
105107

106108
@SuppressWarnings("unchecked")
107-
static <T> ValueObject<T> get(Bindable<T> bindable, BindConstructorProvider constructorProvider) {
109+
static <T> ValueObject<T> get(Bindable<T> bindable, BindConstructorProvider constructorProvider,
110+
Binder.Context context) {
108111
Class<T> type = (Class<T>) bindable.getType().resolve();
109112
if (type == null || type.isEnum() || Modifier.isAbstract(type.getModifiers())) {
110113
return null;
111114
}
112-
Constructor<?> bindConstructor = constructorProvider.getBindConstructor(bindable);
115+
Constructor<?> bindConstructor = constructorProvider.getBindConstructor(bindable,
116+
context.isNestedConstructorBinding());
113117
if (bindConstructor == null) {
114118
return null;
115119
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ void forValueObjectReturnsBean() {
213213
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
214214
assertThat(target.getValue()).isNull();
215215
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
216-
.getBindConstructor(ConstructorBindingOnConstructor.class)).isNotNull();
216+
.getBindConstructor(ConstructorBindingOnConstructor.class, false)).isNotNull();
217217
}
218218

219219
@Test

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

+61
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,18 @@ void loadWhenBeanFactoryContainsSingletonForConstructorBindingTypeShouldNotFail(
907907
load(TestConfiguration.class);
908908
}
909909

910+
@Test
911+
void loadWhenConstructorBindingWithOuterClassDeducedConstructorBound() {
912+
MutablePropertySources sources = this.context.getEnvironment().getPropertySources();
913+
Map<String, Object> source = new HashMap<>();
914+
source.put("test.nested.outer.age", "5");
915+
sources.addLast(new MapPropertySource("test", source));
916+
load(ConstructorBindingWithOuterClassConstructorBoundConfiguration.class);
917+
ConstructorBindingWithOuterClassConstructorBoundProperties bean = this.context
918+
.getBean(ConstructorBindingWithOuterClassConstructorBoundProperties.class);
919+
assertThat(bean.getNested().getOuter().getAge()).isEqualTo(5);
920+
}
921+
910922
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
911923
return load(new Class<?>[] { configuration }, inlinedProperties);
912924
}
@@ -2126,6 +2138,55 @@ static class NestedMultipleConstructorsConfiguration {
21262138

21272139
}
21282140

2141+
@ConfigurationProperties("test")
2142+
@ConstructorBinding
2143+
static class ConstructorBindingWithOuterClassConstructorBoundProperties {
2144+
2145+
private final Nested nested;
2146+
2147+
ConstructorBindingWithOuterClassConstructorBoundProperties(Nested nested) {
2148+
this.nested = nested;
2149+
}
2150+
2151+
Nested getNested() {
2152+
return this.nested;
2153+
}
2154+
2155+
static class Nested {
2156+
2157+
private Outer outer;
2158+
2159+
Outer getOuter() {
2160+
return this.outer;
2161+
}
2162+
2163+
void setOuter(Outer nested) {
2164+
this.outer = nested;
2165+
}
2166+
2167+
}
2168+
2169+
}
2170+
2171+
static class Outer {
2172+
2173+
private int age;
2174+
2175+
Outer(int age) {
2176+
this.age = age;
2177+
}
2178+
2179+
int getAge() {
2180+
return this.age;
2181+
}
2182+
2183+
}
2184+
2185+
@EnableConfigurationProperties(ConstructorBindingWithOuterClassConstructorBoundProperties.class)
2186+
static class ConstructorBindingWithOuterClassConstructorBoundConfiguration {
2187+
2188+
}
2189+
21292190
@ConfigurationProperties("test")
21302191
static class MultiConstructorConfigurationListProperties {
21312192

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,8 @@ void bindToClassWhenNoDefaultConstructorShouldBind() {
339339

340340
@Test
341341
void bindToInstanceWhenNoDefaultConstructorShouldBind() {
342-
Binder binder = new Binder(this.sources, null, null, null, null, (type) -> null);
342+
Binder binder = new Binder(this.sources, null, null, null, null,
343+
(bindable, isNestedConstructorBinding) -> null);
343344
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
344345
source.put("foo.value", "bar");
345346
this.sources.add(source);

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ void bindToClassWithMultipleConstructorsAndFilterShouldBind() {
103103
this.sources.add(source);
104104
Constructor<?>[] constructors = MultipleConstructorsBean.class.getDeclaredConstructors();
105105
Constructor<?> constructor = (constructors[0].getParameterCount() == 1) ? constructors[0] : constructors[1];
106-
Binder binder = new Binder(this.sources, null, null, null, null, (type) -> constructor);
106+
Binder binder = new Binder(this.sources, null, null, null, null,
107+
(bindable, isNestedConstructorBinding) -> constructor);
107108
MultipleConstructorsBean bound = binder.bind("foo", Bindable.of(MultipleConstructorsBean.class)).get();
108109
assertThat(bound.getIntValue()).isEqualTo(12);
109110
}

0 commit comments

Comments
 (0)