Restore constructor binding support with AOT

This commit restores the generation of the BindMethod attribute that
is required at runtime to figure out how to bind a particular
configuration properties target.

It also improves the test to use TestCompiler and assert that the
generated contribution restores the proper behavior for both java
bean and value object binding.

Closes gh-31956
pull/27373/merge
Stephane Nicoll 2 years ago
parent a8c558a671
commit c05d0c51b7

@ -17,6 +17,7 @@
package org.springframework.boot.context.properties;
import java.lang.reflect.Executable;
import java.util.function.Predicate;
import javax.lang.model.element.Modifier;
@ -30,6 +31,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod;
import org.springframework.javapoet.CodeBlock;
@ -59,6 +61,9 @@ class ConfigurationPropertiesBeanRegistrationAotProcessor implements BeanRegistr
private static class ConfigurationPropertiesBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments {
private static final Predicate<String> INCLUDE_BIND_METHOD_ATTRIBUTE_FILTER = (name) -> name
.equals(BindMethod.class.getName());
private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean";
private final RegisteredBean registeredBean;
@ -69,6 +74,14 @@ class ConfigurationPropertiesBeanRegistrationAotProcessor implements BeanRegistr
this.registeredBean = registeredBean;
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode,
beanDefinition, INCLUDE_BIND_METHOD_ATTRIBUTE_FILTER.or(attributeFilter));
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,

@ -16,14 +16,30 @@
package org.springframework.boot.context.properties;
import java.util.Arrays;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generator.compile.CompileWithTargetClassAccess;
import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration;
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration.BFirstProperties;
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration.BSecondProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.javapoet.ClassName;
import org.springframework.test.aot.generate.TestGenerationContext;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -73,6 +89,78 @@ class ConfigurationPropertiesBeanRegistrationAotProcessorTests {
return this.processor.processAheadOfTime(registeredBean);
}
@Test
@CompileWithTargetClassAccess
void aotContributedInitializerBindsValueObject() {
compile(createContext(ValueObjectSampleBeanConfiguration.class), (freshContext) -> {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "test.name=Hello");
freshContext.refresh();
ValueObjectSampleBean bean = freshContext.getBean(ValueObjectSampleBean.class);
assertThat(bean.name).isEqualTo("Hello");
});
}
@Test
@CompileWithTargetClassAccess
void aotContributedInitializerBindsJavaBean() {
compile(createContext(JavaBeanSampleBeanConfiguration.class), (freshContext) -> {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "test.name=Hello");
freshContext.refresh();
JavaBeanSampleBean bean = freshContext.getBean(JavaBeanSampleBean.class);
assertThat(bean.getName()).isEqualTo("Hello");
});
}
@Test
@CompileWithTargetClassAccess
void aotContributedInitializerBindsScannedValueObject() {
compile(createContext(ScanTestConfiguration.class), (freshContext) -> {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "b.first.name=Hello");
freshContext.refresh();
BFirstProperties bean = freshContext.getBean(BFirstProperties.class);
assertThat(bean.getName()).isEqualTo("Hello");
});
}
@Test
@CompileWithTargetClassAccess
void aotContributedInitializerBindsScannedJavaBean() {
compile(createContext(ScanTestConfiguration.class), (freshContext) -> {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "b.second.number=42");
freshContext.refresh();
BSecondProperties bean = freshContext.getBean(BSecondProperties.class);
assertThat(bean.getNumber()).isEqualTo(42);
});
}
private GenericApplicationContext createContext(Class<?>... types) {
GenericApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(JavaBeanSampleBeanConfiguration.class);
Arrays.stream(types).forEach((type) -> context.registerBean(type));
return context;
}
@SuppressWarnings("unchecked")
private void compile(GenericApplicationContext context, Consumer<GenericApplicationContext> freshContext) {
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
ClassName className = new ApplicationContextAotGenerator().generateApplicationContext(context,
generationContext);
generationContext.writeGeneratedContent();
TestCompiler.forSystem().withFiles(generationContext.getGeneratedFiles()).compile((compiled) -> {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
.getInstance(ApplicationContextInitializer.class, className.toString());
initializer.initialize(freshApplicationContext);
freshContext.accept(freshApplicationContext);
});
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JavaBeanSampleBean.class)
static class JavaBeanSampleBeanConfiguration {
}
@ConfigurationProperties("test")
public static class JavaBeanSampleBean {
@ -88,6 +176,12 @@ class ConfigurationPropertiesBeanRegistrationAotProcessorTests {
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ValueObjectSampleBean.class)
static class ValueObjectSampleBeanConfiguration {
}
@ConfigurationProperties("test")
public static class ValueObjectSampleBean {
@ -100,4 +194,14 @@ class ConfigurationPropertiesBeanRegistrationAotProcessorTests {
}
@Configuration(proxyBeanMethods = false)
@ConfigurationPropertiesScan(basePackageClasses = BScanConfiguration.class)
static class ScanTestConfiguration {
}
static class TestTarget {
}
}

Loading…
Cancel
Save