Merge branch '3.1.x'
Closes gh-37337
This commit is contained in:
@@ -16,14 +16,18 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.boot.context.properties.bind.BindConstructorProvider;
|
||||
import org.springframework.boot.context.properties.bind.BindMethod;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
|
||||
@@ -43,6 +47,7 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be
|
||||
@Override
|
||||
public ConfigurationPropertiesReflectionHintsContribution processAheadOfTime(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
beanFactory.addBeanPostProcessor(new BindConstructorAwareBeanPostProcessor(beanFactory));
|
||||
String[] beanNames = beanFactory.getBeanNamesForAnnotation(ConfigurationProperties.class);
|
||||
List<Bindable<?>> bindables = new ArrayList<>();
|
||||
for (String beanName : beanNames) {
|
||||
@@ -58,6 +63,37 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be
|
||||
return (!bindables.isEmpty()) ? new ConfigurationPropertiesReflectionHintsContribution(bindables) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SmartInstantiationAwareBeanPostProcessor} implementation to work around
|
||||
* framework's constructor resolver for immutable configuration properties.
|
||||
* <p>
|
||||
* Constructor binding supports multiple constructors as long as one is identified as
|
||||
* the candidate for binding. Unfortunately, framework is not aware of such feature
|
||||
* and attempts to resolve the autowired constructor to use.
|
||||
*/
|
||||
static class BindConstructorAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
BindConstructorAwareBeanPostProcessor(ConfigurableListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
|
||||
throws BeansException {
|
||||
BindMethod bindMethod = BindMethodAttribute.get(this.beanFactory, beanName);
|
||||
if (bindMethod != null && bindMethod == BindMethod.VALUE_OBJECT) {
|
||||
Constructor<?> bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(beanClass, false);
|
||||
if (bindConstructor != null) {
|
||||
return new Constructor<?>[] { bindConstructor };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class ConfigurationPropertiesReflectionHintsContribution
|
||||
implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
|
||||
@@ -100,6 +100,20 @@ class ConfigurationPropertiesBeanRegistrationAotProcessorTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void aotContributedInitializerBindsValueObjectWithSpecificConstructor() {
|
||||
compile(createContext(ValueObjectSampleBeanWithSpecificConstructorConfiguration.class), (freshContext) -> {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "test.name=Hello",
|
||||
"test.counter=30");
|
||||
freshContext.refresh();
|
||||
ValueObjectWithSpecificConstructorSampleBean bean = freshContext
|
||||
.getBean(ValueObjectWithSpecificConstructorSampleBean.class);
|
||||
assertThat(bean.name).isEqualTo("Hello");
|
||||
assertThat(bean.counter).isEqualTo(30);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void aotContributedInitializerBindsJavaBean() {
|
||||
@@ -193,6 +207,32 @@ class ConfigurationPropertiesBeanRegistrationAotProcessorTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(ValueObjectWithSpecificConstructorSampleBean.class)
|
||||
static class ValueObjectSampleBeanWithSpecificConstructorConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
public static class ValueObjectWithSpecificConstructorSampleBean {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String name;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Integer counter;
|
||||
|
||||
ValueObjectWithSpecificConstructorSampleBean(String name, Integer counter) {
|
||||
this.name = name;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
private ValueObjectWithSpecificConstructorSampleBean(String name) {
|
||||
this(name, 42);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConfigurationPropertiesScan(basePackageClasses = BScanConfiguration.class)
|
||||
static class ScanTestConfiguration {
|
||||
|
||||
Reference in New Issue
Block a user