Browse Source
This commit fixes a few bugs related to constructor binding. The ContructorFilter on the Bindable has been replaced with a Binder level BinderConstructorProvider so that it can be used to determine the constructor to use for nested properties as well. Fixes gh-18810 Fixes gh-18670 Closes gh-18685 Closes gh-18894 Co-authored-by: Phillip Webb <pwebb@pivotal.io>pull/18895/head
16 changed files with 518 additions and 165 deletions
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/* |
||||
* Copyright 2012-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.context.properties; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
|
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.boot.context.properties.bind.BindConstructorProvider; |
||||
import org.springframework.boot.context.properties.bind.Bindable; |
||||
import org.springframework.core.KotlinDetector; |
||||
import org.springframework.core.annotation.MergedAnnotations; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* {@link BindConstructorProvider} used when binding |
||||
* {@link ConfigurationProperties @ConfigurationProperties}. |
||||
* |
||||
* @author Madhura Bhave |
||||
* @author Phillip Webb |
||||
*/ |
||||
class ConfigurationPropertiesBindConstructorProvider implements BindConstructorProvider { |
||||
|
||||
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider(); |
||||
|
||||
@Override |
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable) { |
||||
return getBindConstructor(bindable.getType().resolve()); |
||||
} |
||||
|
||||
Constructor<?> getBindConstructor(Class<?> type) { |
||||
if (type == null) { |
||||
return null; |
||||
} |
||||
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type); |
||||
if (constructor == null && isConstructorBindingAnnotatedType(type)) { |
||||
constructor = deduceBindConstructor(type); |
||||
} |
||||
return constructor; |
||||
} |
||||
|
||||
private Constructor<?> findConstructorBindingAnnotatedConstructor(Class<?> type) { |
||||
if (isKotlinType(type)) { |
||||
Constructor<?> constructor = BeanUtils.findPrimaryConstructor(type); |
||||
if (constructor != null) { |
||||
return findAnnotatedConstructor(type, constructor); |
||||
} |
||||
} |
||||
return findAnnotatedConstructor(type, type.getDeclaredConstructors()); |
||||
} |
||||
|
||||
private Constructor<?> findAnnotatedConstructor(Class<?> type, Constructor<?>... candidates) { |
||||
Constructor<?> constructor = null; |
||||
for (Constructor<?> candidate : candidates) { |
||||
if (MergedAnnotations.from(candidate).isPresent(ConstructorBinding.class)) { |
||||
Assert.state(candidate.getParameterCount() > 0, |
||||
type.getName() + " declares @ConstructorBinding on a no-args constructor"); |
||||
Assert.state(constructor == null, |
||||
type.getName() + " has more than one @ConstructorBinding constructor"); |
||||
constructor = candidate; |
||||
} |
||||
} |
||||
return constructor; |
||||
} |
||||
|
||||
private boolean isConstructorBindingAnnotatedType(Class<?> type) { |
||||
return MergedAnnotations.from(type, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES) |
||||
.isPresent(ConstructorBinding.class); |
||||
} |
||||
|
||||
private Constructor<?> deduceBindConstructor(Class<?> type) { |
||||
if (isKotlinType(type)) { |
||||
return deducedKotlinBindConstructor(type); |
||||
} |
||||
Constructor<?>[] constructors = type.getDeclaredConstructors(); |
||||
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) { |
||||
return constructors[0]; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private Constructor<?> deducedKotlinBindConstructor(Class<?> type) { |
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type); |
||||
if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { |
||||
return primaryConstructor; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private boolean isKotlinType(Class<?> type) { |
||||
return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* Copyright 2012-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.context.properties.bind; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
|
||||
/** |
||||
* Strategy interface used to determine a specific constructor to use when binding. |
||||
* |
||||
* @author Madhura Bhave |
||||
* @since 2.2.1 |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface BindConstructorProvider { |
||||
|
||||
/** |
||||
* Default {@link BindConstructorProvider} implementation that only returns a value |
||||
* when there's a single constructor and when the bindable has no existing value. |
||||
*/ |
||||
BindConstructorProvider DEFAULT = new DefaultBindConstructorProvider(); |
||||
|
||||
/** |
||||
* 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 |
||||
* @return the bind constructor or {@code null} |
||||
*/ |
||||
Constructor<?> getBindConstructor(Bindable<?> bindable); |
||||
|
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
/* |
||||
* Copyright 2012-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.context.properties.bind; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
|
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.core.KotlinDetector; |
||||
|
||||
/** |
||||
* Default {@link BindConstructorProvider} implementation. |
||||
* |
||||
* @author Madhura Bhave |
||||
* @author Phillip Webb |
||||
*/ |
||||
class DefaultBindConstructorProvider implements BindConstructorProvider { |
||||
|
||||
@Override |
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable) { |
||||
Class<?> type = bindable.getType().resolve(); |
||||
if (bindable.getValue() != null || type == null) { |
||||
return null; |
||||
} |
||||
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type)) { |
||||
return getDeducedKotlinConstructor(type); |
||||
} |
||||
Constructor<?>[] constructors = type.getDeclaredConstructors(); |
||||
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) { |
||||
return constructors[0]; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private Constructor<?> getDeducedKotlinConstructor(Class<?> type) { |
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type); |
||||
if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { |
||||
return primaryConstructor; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue