commit
c8dfec4b38
|
|
@ -308,7 +308,7 @@ public final class ConfigurationPropertiesBean {
|
||||||
VALUE_OBJECT;
|
VALUE_OBJECT;
|
||||||
|
|
||||||
static BindMethod forType(Class<?> type) {
|
static BindMethod forType(Class<?> type) {
|
||||||
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type) != null)
|
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type, false) != null)
|
||||||
? VALUE_OBJECT : JAVA_BEAN;
|
? VALUE_OBJECT : JAVA_BEAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,16 +37,16 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
|
||||||
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
|
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Constructor<?> getBindConstructor(Bindable<?> bindable) {
|
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
||||||
return getBindConstructor(bindable.getType().resolve());
|
return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
Constructor<?> getBindConstructor(Class<?> type) {
|
Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
|
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
|
||||||
if (constructor == null && isConstructorBindingAnnotatedType(type)) {
|
if (constructor == null && (isConstructorBindingAnnotatedType(type) || isNestedConstructorBinding)) {
|
||||||
constructor = deduceBindConstructor(type);
|
constructor = deduceBindConstructor(type);
|
||||||
}
|
}
|
||||||
return constructor;
|
return constructor;
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,10 @@ public interface BindConstructorProvider {
|
||||||
* Return the bind constructor to use for the given bindable, or {@code null} if
|
* Return the bind constructor to use for the given bindable, or {@code null} if
|
||||||
* constructor binding is not supported.
|
* constructor binding is not supported.
|
||||||
* @param bindable the bindable to check
|
* @param bindable the bindable to check
|
||||||
|
* @param isNestedConstructorBinding if this binding is nested within a constructor
|
||||||
|
* binding
|
||||||
* @return the bind constructor or {@code null}
|
* @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<?>> dataObjectBindings = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private final Deque<Class<?>> constructorBindings = new ArrayDeque<>();
|
||||||
|
|
||||||
private ConfigurationProperty configurationProperty;
|
private ConfigurationProperty configurationProperty;
|
||||||
|
|
||||||
Context() {
|
Context() {
|
||||||
|
|
@ -582,6 +584,18 @@ public class Binder {
|
||||||
this.configurationProperty = null;
|
this.configurationProperty = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pushConstructorBoundTypes(Class<?> value) {
|
||||||
|
this.constructorBindings.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isNestedConstructorBinding() {
|
||||||
|
return !this.constructorBindings.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void popConstructorBoundTypes() {
|
||||||
|
this.constructorBindings.pop();
|
||||||
|
}
|
||||||
|
|
||||||
PlaceholdersResolver getPlaceholdersResolver() {
|
PlaceholdersResolver getPlaceholdersResolver() {
|
||||||
return Binder.this.placeholdersResolver;
|
return Binder.this.placeholdersResolver;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import org.springframework.core.KotlinDetector;
|
||||||
class DefaultBindConstructorProvider implements BindConstructorProvider {
|
class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Constructor<?> getBindConstructor(Bindable<?> bindable) {
|
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
||||||
Class<?> type = bindable.getType().resolve();
|
Class<?> type = bindable.getType().resolve();
|
||||||
if (bindable.getValue() != null || type == null) {
|
if (bindable.getValue() != null || type == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,11 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||||
@Override
|
@Override
|
||||||
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Context context,
|
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Context context,
|
||||||
DataObjectPropertyBinder propertyBinder) {
|
DataObjectPropertyBinder propertyBinder) {
|
||||||
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider);
|
ValueObject<T> valueObject = ValueObject.get(target, this.constructorProvider, context);
|
||||||
if (valueObject == null) {
|
if (valueObject == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
context.pushConstructorBoundTypes(target.getType().resolve());
|
||||||
List<ConstructorParameter> parameters = valueObject.getConstructorParameters();
|
List<ConstructorParameter> parameters = valueObject.getConstructorParameters();
|
||||||
List<Object> args = new ArrayList<>(parameters.size());
|
List<Object> args = new ArrayList<>(parameters.size());
|
||||||
boolean bound = false;
|
boolean bound = false;
|
||||||
|
|
@ -67,12 +68,13 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||||
args.add(arg);
|
args.add(arg);
|
||||||
}
|
}
|
||||||
context.clearConfigurationProperty();
|
context.clearConfigurationProperty();
|
||||||
|
context.popConstructorBoundTypes();
|
||||||
return bound ? valueObject.instantiate(args) : null;
|
return bound ? valueObject.instantiate(args) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T create(Bindable<T> target, Binder.Context context) {
|
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) {
|
if (valueObject == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -104,12 +106,14 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||||
abstract List<ConstructorParameter> getConstructorParameters();
|
abstract List<ConstructorParameter> getConstructorParameters();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@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();
|
Class<T> type = (Class<T>) bindable.getType().resolve();
|
||||||
if (type == null || type.isEnum() || Modifier.isAbstract(type.getModifiers())) {
|
if (type == null || type.isEnum() || Modifier.isAbstract(type.getModifiers())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Constructor<?> bindConstructor = constructorProvider.getBindConstructor(bindable);
|
Constructor<?> bindConstructor = constructorProvider.getBindConstructor(bindable,
|
||||||
|
context.isNestedConstructorBinding());
|
||||||
if (bindConstructor == null) {
|
if (bindConstructor == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ class ConfigurationPropertiesBeanTests {
|
||||||
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
|
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
|
||||||
assertThat(target.getValue()).isNull();
|
assertThat(target.getValue()).isNull();
|
||||||
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
|
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
|
||||||
.getBindConstructor(ConstructorBindingOnConstructor.class)).isNotNull();
|
.getBindConstructor(ConstructorBindingOnConstructor.class, false)).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -907,6 +907,18 @@ class ConfigurationPropertiesTests {
|
||||||
load(TestConfiguration.class);
|
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) {
|
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
|
||||||
return load(new Class<?>[] { configuration }, 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")
|
@ConfigurationProperties("test")
|
||||||
static class MultiConstructorConfigurationListProperties {
|
static class MultiConstructorConfigurationListProperties {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -339,7 +339,8 @@ class JavaBeanBinderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void bindToInstanceWhenNoDefaultConstructorShouldBind() {
|
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();
|
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||||
source.put("foo.value", "bar");
|
source.put("foo.value", "bar");
|
||||||
this.sources.add(source);
|
this.sources.add(source);
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ class ValueObjectBinderTests {
|
||||||
this.sources.add(source);
|
this.sources.add(source);
|
||||||
Constructor<?>[] constructors = MultipleConstructorsBean.class.getDeclaredConstructors();
|
Constructor<?>[] constructors = MultipleConstructorsBean.class.getDeclaredConstructors();
|
||||||
Constructor<?> constructor = (constructors[0].getParameterCount() == 1) ? constructors[0] : constructors[1];
|
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();
|
MultipleConstructorsBean bound = binder.bind("foo", Bindable.of(MultipleConstructorsBean.class)).get();
|
||||||
assertThat(bound.getIntValue()).isEqualTo(12);
|
assertThat(bound.getIntValue()).isEqualTo(12);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue