mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-15 01:07:30 +08:00
Merge branch '2.2.x'
Closes gh-19011
This commit is contained in:
commit
c8dfec4b38
@ -308,7 +308,7 @@ public final class ConfigurationPropertiesBean {
|
||||
VALUE_OBJECT;
|
||||
|
||||
static BindMethod forType(Class<?> type) {
|
||||
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type) != null)
|
||||
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type, false) != null)
|
||||
? VALUE_OBJECT : JAVA_BEAN;
|
||||
}
|
||||
|
||||
|
@ -37,16 +37,16 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
|
||||
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
|
||||
|
||||
@Override
|
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable) {
|
||||
return getBindConstructor(bindable.getType().resolve());
|
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
||||
return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding);
|
||||
}
|
||||
|
||||
Constructor<?> getBindConstructor(Class<?> type) {
|
||||
Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
|
||||
if (constructor == null && isConstructorBindingAnnotatedType(type)) {
|
||||
if (constructor == null && (isConstructorBindingAnnotatedType(type) || isNestedConstructorBinding)) {
|
||||
constructor = deduceBindConstructor(type);
|
||||
}
|
||||
return constructor;
|
||||
|
@ -37,8 +37,10 @@ public interface BindConstructorProvider {
|
||||
* Return the bind constructor to use for the given bindable, or {@code null} if
|
||||
* constructor binding is not supported.
|
||||
* @param bindable the bindable to check
|
||||
* @param isNestedConstructorBinding if this binding is nested within a constructor
|
||||
* binding
|
||||
* @return the bind constructor or {@code null}
|
||||
*/
|
||||
Constructor<?> getBindConstructor(Bindable<?> bindable);
|
||||
Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding);
|
||||
|
||||
}
|
||||
|
@ -522,6 +522,8 @@ public class Binder {
|
||||
|
||||
private final Deque<Class<?>> dataObjectBindings = new ArrayDeque<>();
|
||||
|
||||
private final Deque<Class<?>> constructorBindings = new ArrayDeque<>();
|
||||
|
||||
private ConfigurationProperty configurationProperty;
|
||||
|
||||
Context() {
|
||||
@ -582,6 +584,18 @@ public class Binder {
|
||||
this.configurationProperty = null;
|
||||
}
|
||||
|
||||
void pushConstructorBoundTypes(Class<?> value) {
|
||||
this.constructorBindings.push(value);
|
||||
}
|
||||
|
||||
boolean isNestedConstructorBinding() {
|
||||
return !this.constructorBindings.isEmpty();
|
||||
}
|
||||
|
||||
void popConstructorBoundTypes() {
|
||||
this.constructorBindings.pop();
|
||||
}
|
||||
|
||||
PlaceholdersResolver getPlaceholdersResolver() {
|
||||
return Binder.this.placeholdersResolver;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import org.springframework.core.KotlinDetector;
|
||||
class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||
|
||||
@Override
|
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable) {
|
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
||||
Class<?> type = bindable.getType().resolve();
|
||||
if (bindable.getValue() != null || type == null) {
|
||||
return null;
|
||||
|
@ -53,10 +53,11 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||
@Override
|
||||
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Context context,
|
||||
DataObjectPropertyBinder propertyBinder) {
|
||||
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider);
|
||||
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider, context);
|
||||
if (valueObject == null) {
|
||||
return null;
|
||||
}
|
||||
context.pushConstructorBoundTypes(target.getType().resolve());
|
||||
List<ConstructorParameter> parameters = valueObject.getConstructorParameters();
|
||||
List<Object> args = new ArrayList<>(parameters.size());
|
||||
boolean bound = false;
|
||||
@ -67,12 +68,13 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||
args.add(arg);
|
||||
}
|
||||
context.clearConfigurationProperty();
|
||||
context.popConstructorBoundTypes();
|
||||
return bound ? valueObject.instantiate(args) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T create(Bindable<T> target, Binder.Context context) {
|
||||
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider);
|
||||
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider, context);
|
||||
if (valueObject == null) {
|
||||
return null;
|
||||
}
|
||||
@ -104,12 +106,14 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||
abstract List<ConstructorParameter> getConstructorParameters();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> ValueObject<T> get(Bindable<T> bindable, BindConstructorProvider constructorProvider) {
|
||||
static <T> ValueObject<T> get(Bindable<T> bindable, BindConstructorProvider constructorProvider,
|
||||
Binder.Context context) {
|
||||
Class<T> type = (Class<T>) bindable.getType().resolve();
|
||||
if (type == null || type.isEnum() || Modifier.isAbstract(type.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
Constructor<?> bindConstructor = constructorProvider.getBindConstructor(bindable);
|
||||
Constructor<?> bindConstructor = constructorProvider.getBindConstructor(bindable,
|
||||
context.isNestedConstructorBinding());
|
||||
if (bindConstructor == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ class ConfigurationPropertiesBeanTests {
|
||||
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
|
||||
assertThat(target.getValue()).isNull();
|
||||
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
|
||||
.getBindConstructor(ConstructorBindingOnConstructor.class)).isNotNull();
|
||||
.getBindConstructor(ConstructorBindingOnConstructor.class, false)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -907,6 +907,18 @@ class ConfigurationPropertiesTests {
|
||||
load(TestConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenConstructorBindingWithOuterClassDeducedConstructorBound() {
|
||||
MutablePropertySources sources = this.context.getEnvironment().getPropertySources();
|
||||
Map<String, Object> source = new HashMap<>();
|
||||
source.put("test.nested.outer.age", "5");
|
||||
sources.addLast(new MapPropertySource("test", source));
|
||||
load(ConstructorBindingWithOuterClassConstructorBoundConfiguration.class);
|
||||
ConstructorBindingWithOuterClassConstructorBoundProperties bean = this.context
|
||||
.getBean(ConstructorBindingWithOuterClassConstructorBoundProperties.class);
|
||||
assertThat(bean.getNested().getOuter().getAge()).isEqualTo(5);
|
||||
}
|
||||
|
||||
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
|
||||
return load(new Class<?>[] { configuration }, inlinedProperties);
|
||||
}
|
||||
@ -2126,6 +2138,55 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
@ConstructorBinding
|
||||
static class ConstructorBindingWithOuterClassConstructorBoundProperties {
|
||||
|
||||
private final Nested nested;
|
||||
|
||||
ConstructorBindingWithOuterClassConstructorBoundProperties(Nested nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
static class Nested {
|
||||
|
||||
private Outer outer;
|
||||
|
||||
Outer getOuter() {
|
||||
return this.outer;
|
||||
}
|
||||
|
||||
void setOuter(Outer nested) {
|
||||
this.outer = nested;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Outer {
|
||||
|
||||
private int age;
|
||||
|
||||
Outer(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties(ConstructorBindingWithOuterClassConstructorBoundProperties.class)
|
||||
static class ConstructorBindingWithOuterClassConstructorBoundConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
static class MultiConstructorConfigurationListProperties {
|
||||
|
||||
|
@ -339,7 +339,8 @@ class JavaBeanBinderTests {
|
||||
|
||||
@Test
|
||||
void bindToInstanceWhenNoDefaultConstructorShouldBind() {
|
||||
Binder binder = new Binder(this.sources, null, null, null, null, (type) -> null);
|
||||
Binder binder = new Binder(this.sources, null, null, null, null,
|
||||
(bindable, isNestedConstructorBinding) -> null);
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value", "bar");
|
||||
this.sources.add(source);
|
||||
|
@ -103,7 +103,8 @@ class ValueObjectBinderTests {
|
||||
this.sources.add(source);
|
||||
Constructor<?>[] constructors = MultipleConstructorsBean.class.getDeclaredConstructors();
|
||||
Constructor<?> constructor = (constructors[0].getParameterCount() == 1) ? constructors[0] : constructors[1];
|
||||
Binder binder = new Binder(this.sources, null, null, null, null, (type) -> constructor);
|
||||
Binder binder = new Binder(this.sources, null, null, null, null,
|
||||
(bindable, isNestedConstructorBinding) -> constructor);
|
||||
MultipleConstructorsBean bound = binder.bind("foo", Bindable.of(MultipleConstructorsBean.class)).get();
|
||||
assertThat(bound.getIntValue()).isEqualTo(12);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user