From c05d0c51b76167c13fdc95a067b0cc74ebbc9577 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 2 Aug 2022 14:48:22 +0200 Subject: [PATCH] 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 --- ...ropertiesBeanRegistrationAotProcessor.java | 13 +++ ...tiesBeanRegistrationAotProcessorTests.java | 104 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java index 713c19e04c0..97e53697300 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java @@ -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 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 attributeFilter) { + return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode, + beanDefinition, INCLUDE_BIND_METHOD_ATTRIBUTE_FILTER.or(attributeFilter)); + } + @Override public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod, diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessorTests.java index 3f4ee6e0085..ead75109b85 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessorTests.java @@ -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 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 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 { + + } + }