Support AOT processing of Value Object with several constructors
Previously, AOT processing failed on processing an immutable configuration properties that declare several constructors as the core framework infrastructure tries to resolve the "autowired" constructor to use, even if the custom code fragments are never going to use it. This commit workarounds the problem in maintenance releases until a proper fix is provided in the core framework. When AOT runs, a SmartInstantiationAwareBeanPostProcessor is added to the bean factory to provide the constructor to use. This implementation relies on the same algorithm that the binder uses at runtime. Closes gh-37283
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