Merge branch '3.1.x'

Closes gh-37337
This commit is contained in:
Stephane Nicoll
2023-09-11 15:37:53 +02:00
2 changed files with 76 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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 {