Browse Source
Relocate `@ConstructorBinding` from the `boot.context.properties` package to `boot.context.properties.bind` and update the `DefaultBindConstructorProvider` to support it. Closes gh-32660pull/33505/head
19 changed files with 452 additions and 250 deletions
@ -1,163 +0,0 @@
@@ -1,163 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2022 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 java.util.Arrays; |
||||
|
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
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 |
||||
* @since 3.0.0 |
||||
*/ |
||||
public class ConfigurationPropertiesBindConstructorProvider implements BindConstructorProvider { |
||||
|
||||
/** |
||||
* A shared singleton {@link ConfigurationPropertiesBindConstructorProvider} instance. |
||||
*/ |
||||
public static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider(); |
||||
|
||||
@Override |
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) { |
||||
return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding); |
||||
} |
||||
|
||||
Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) { |
||||
if (type == null) { |
||||
return null; |
||||
} |
||||
Constructors constructors = Constructors.getConstructors(type); |
||||
if (constructors.getBind() != null || isNestedConstructorBinding) { |
||||
Assert.state(!constructors.hasAutowired(), |
||||
() -> type.getName() + " declares @ConstructorBinding and @Autowired constructor"); |
||||
} |
||||
return constructors.getBind(); |
||||
} |
||||
|
||||
/** |
||||
* Data holder for autowired and bind constructors. |
||||
*/ |
||||
static final class Constructors { |
||||
|
||||
private final boolean hasAutowired; |
||||
|
||||
private final Constructor<?> bind; |
||||
|
||||
private Constructors(boolean hasAutowired, Constructor<?> bind) { |
||||
this.hasAutowired = hasAutowired; |
||||
this.bind = bind; |
||||
} |
||||
|
||||
boolean hasAutowired() { |
||||
return this.hasAutowired; |
||||
} |
||||
|
||||
Constructor<?> getBind() { |
||||
return this.bind; |
||||
} |
||||
|
||||
static Constructors getConstructors(Class<?> type) { |
||||
Constructor<?>[] candidates = getCandidateConstructors(type); |
||||
Constructor<?> deducedBind = deduceBindConstructor(candidates); |
||||
if (deducedBind != null) { |
||||
return new Constructors(false, deducedBind); |
||||
} |
||||
boolean hasAutowiredConstructor = false; |
||||
Constructor<?> bind = null; |
||||
for (Constructor<?> candidate : candidates) { |
||||
if (isAutowired(candidate)) { |
||||
hasAutowiredConstructor = true; |
||||
continue; |
||||
} |
||||
bind = findAnnotatedConstructor(type, bind, candidate); |
||||
} |
||||
if (bind == null && !hasAutowiredConstructor && isKotlinType(type)) { |
||||
bind = deduceKotlinBindConstructor(type); |
||||
} |
||||
return new Constructors(hasAutowiredConstructor, bind); |
||||
} |
||||
|
||||
private static Constructor<?>[] getCandidateConstructors(Class<?> type) { |
||||
if (isInnerClass(type)) { |
||||
return new Constructor<?>[0]; |
||||
} |
||||
return Arrays.stream(type.getDeclaredConstructors()) |
||||
.filter((constructor) -> isNonSynthetic(constructor, type)).toArray(Constructor[]::new); |
||||
} |
||||
|
||||
private static boolean isInnerClass(Class<?> type) { |
||||
try { |
||||
return type.getDeclaredField("this$0").isSynthetic(); |
||||
} |
||||
catch (NoSuchFieldException ex) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private static boolean isNonSynthetic(Constructor<?> constructor, Class<?> type) { |
||||
return !constructor.isSynthetic(); |
||||
} |
||||
|
||||
private static Constructor<?> deduceBindConstructor(Constructor<?>[] constructors) { |
||||
if (constructors.length == 1 && constructors[0].getParameterCount() > 0 && !isAutowired(constructors[0])) { |
||||
return constructors[0]; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private static boolean isAutowired(Constructor<?> candidate) { |
||||
return MergedAnnotations.from(candidate).isPresent(Autowired.class); |
||||
} |
||||
|
||||
private static Constructor<?> findAnnotatedConstructor(Class<?> type, Constructor<?> constructor, |
||||
Constructor<?> candidate) { |
||||
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 static boolean isKotlinType(Class<?> type) { |
||||
return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type); |
||||
} |
||||
|
||||
private static Constructor<?> deduceKotlinBindConstructor(Class<?> type) { |
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type); |
||||
if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { |
||||
return primaryConstructor; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/* |
||||
* Copyright 2012-2022 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.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* Annotation that can be used to indicate which constructor to use when binding |
||||
* configuration properties using constructor arguments rather than by calling setters. A |
||||
* single parameterized constructor implicitly indicates that constructor binding should |
||||
* be used unless the constructor is annotated with `@Autowired`. |
||||
* |
||||
* @author Phillip Webb |
||||
* @since 3.0.0 |
||||
*/ |
||||
@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
public @interface ConstructorBinding { |
||||
|
||||
} |
||||
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Copyright 2012-2022 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.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultBindConstructorProvider}. |
||||
* |
||||
* @author Phillip Webb |
||||
* @author Madhura Bhave |
||||
*/ |
||||
class DefaultBindConstructorProviderTests { |
||||
|
||||
private DefaultBindConstructorProvider provider = new DefaultBindConstructorProvider(); |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasOnlyDefaultConstructorReturnsNull() { |
||||
Constructor<?> constructor = this.provider.getBindConstructor(OnlyDefaultConstructor.class, false); |
||||
assertThat(constructor).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasMultipleAmbiguousConstructorsReturnsNull() { |
||||
Constructor<?> constructor = this.provider.getBindConstructor(MultipleAmbiguousConstructors.class, false); |
||||
assertThat(constructor).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasTwoConstructorsWithOneConstructorBindingReturnsConstructor() { |
||||
Constructor<?> constructor = this.provider.getBindConstructor(TwoConstructorsWithOneConstructorBinding.class, |
||||
false); |
||||
assertThat(constructor).isNotNull(); |
||||
assertThat(constructor.getParameterCount()).isEqualTo(1); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasOneConstructorWithAutowiredReturnsNull() { |
||||
Constructor<?> constructor = this.provider.getBindConstructor(OneConstructorWithAutowired.class, false); |
||||
assertThat(constructor).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasTwoConstructorsWithOneAutowiredReturnsNull() { |
||||
Constructor<?> constructor = this.provider.getBindConstructor(TwoConstructorsWithOneAutowired.class, false); |
||||
assertThat(constructor).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasTwoConstructorsWithOneAutowiredAndOneConstructorBindingThrowsException() { |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> this.provider |
||||
.getBindConstructor(TwoConstructorsWithOneAutowiredAndOneConstructorBinding.class, false)) |
||||
.withMessageContaining("declares @ConstructorBinding and @Autowired"); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasOneConstructorWithConstructorBindingReturnsConstructor() { |
||||
Constructor<?> constructor = this.provider.getBindConstructor(OneConstructorWithConstructorBinding.class, |
||||
false); |
||||
assertThat(constructor).isNotNull(); |
||||
} |
||||
|
||||
@Test |
||||
void getBindConstructorWhenHasTwoConstructorsWithBothConstructorBindingThrowsException() { |
||||
assertThatIllegalStateException() |
||||
.isThrownBy( |
||||
() -> this.provider.getBindConstructor(TwoConstructorsWithBothConstructorBinding.class, false)) |
||||
.withMessageContaining("has more than one @ConstructorBinding"); |
||||
} |
||||
|
||||
static class OnlyDefaultConstructor { |
||||
|
||||
} |
||||
|
||||
static class MultipleAmbiguousConstructors { |
||||
|
||||
MultipleAmbiguousConstructors() { |
||||
} |
||||
|
||||
MultipleAmbiguousConstructors(String name) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class TwoConstructorsWithOneConstructorBinding { |
||||
|
||||
@ConstructorBinding |
||||
TwoConstructorsWithOneConstructorBinding(String name) { |
||||
this(name, 100); |
||||
} |
||||
|
||||
TwoConstructorsWithOneConstructorBinding(String name, int age) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class OneConstructorWithAutowired { |
||||
|
||||
@Autowired |
||||
OneConstructorWithAutowired(String name, int age) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class TwoConstructorsWithOneAutowired { |
||||
|
||||
@Autowired |
||||
TwoConstructorsWithOneAutowired(String name) { |
||||
this(name, 100); |
||||
} |
||||
|
||||
TwoConstructorsWithOneAutowired(String name, int age) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class TwoConstructorsWithOneAutowiredAndOneConstructorBinding { |
||||
|
||||
@Autowired |
||||
TwoConstructorsWithOneAutowiredAndOneConstructorBinding(String name) { |
||||
this(name, 100); |
||||
} |
||||
|
||||
@ConstructorBinding |
||||
TwoConstructorsWithOneAutowiredAndOneConstructorBinding(String name, int age) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class OneConstructorWithConstructorBinding { |
||||
|
||||
@ConstructorBinding |
||||
OneConstructorWithConstructorBinding(String name, int age) { |
||||
} |
||||
|
||||
} |
||||
|
||||
static class TwoConstructorsWithBothConstructorBinding { |
||||
|
||||
@ConstructorBinding |
||||
TwoConstructorsWithBothConstructorBinding(String name) { |
||||
this(name, 100); |
||||
} |
||||
|
||||
@ConstructorBinding |
||||
TwoConstructorsWithBothConstructorBinding(String name, int age) { |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue