Skip to content

Commit c05d0c5

Browse files
committed
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
1 parent a8c558a commit c05d0c5

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.context.properties;
1818

1919
import java.lang.reflect.Executable;
20+
import java.util.function.Predicate;
2021

2122
import javax.lang.model.element.Modifier;
2223

@@ -30,6 +31,7 @@
3031
import org.springframework.beans.factory.config.BeanDefinition;
3132
import org.springframework.beans.factory.support.InstanceSupplier;
3233
import org.springframework.beans.factory.support.RegisteredBean;
34+
import org.springframework.beans.factory.support.RootBeanDefinition;
3335
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod;
3436
import org.springframework.javapoet.CodeBlock;
3537

@@ -59,6 +61,9 @@ private boolean isImmutableConfigurationPropertiesBeanDefinition(BeanDefinition
5961

6062
private static class ConfigurationPropertiesBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments {
6163

64+
private static final Predicate<String> INCLUDE_BIND_METHOD_ATTRIBUTE_FILTER = (name) -> name
65+
.equals(BindMethod.class.getName());
66+
6267
private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean";
6368

6469
private final RegisteredBean registeredBean;
@@ -69,6 +74,14 @@ private static class ConfigurationPropertiesBeanRegistrationCodeFragments extend
6974
this.registeredBean = registeredBean;
7075
}
7176

77+
@Override
78+
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
79+
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
80+
Predicate<String> attributeFilter) {
81+
return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode,
82+
beanDefinition, INCLUDE_BIND_METHOD_ATTRIBUTE_FILTER.or(attributeFilter));
83+
}
84+
7285
@Override
7386
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
7487
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessorTests.java

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,30 @@
1616

1717
package org.springframework.boot.context.properties;
1818

19+
import java.util.Arrays;
20+
import java.util.function.Consumer;
21+
1922
import org.junit.jupiter.api.Test;
2023

24+
import org.springframework.aot.test.generator.compile.CompileWithTargetClassAccess;
25+
import org.springframework.aot.test.generator.compile.TestCompiler;
2126
import org.springframework.beans.factory.aot.AotServices;
2227
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
2328
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
2429
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2530
import org.springframework.beans.factory.support.RegisteredBean;
2631
import org.springframework.beans.factory.support.RootBeanDefinition;
32+
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration;
33+
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration.BFirstProperties;
34+
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration.BSecondProperties;
35+
import org.springframework.context.ApplicationContextInitializer;
36+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
37+
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.context.aot.ApplicationContextAotGenerator;
39+
import org.springframework.context.support.GenericApplicationContext;
40+
import org.springframework.javapoet.ClassName;
41+
import org.springframework.test.aot.generate.TestGenerationContext;
42+
import org.springframework.test.context.support.TestPropertySourceUtils;
2743

2844
import static org.assertj.core.api.Assertions.assertThat;
2945

@@ -73,6 +89,78 @@ private BeanRegistrationAotContribution process(Class<?> type) {
7389
return this.processor.processAheadOfTime(registeredBean);
7490
}
7591

92+
@Test
93+
@CompileWithTargetClassAccess
94+
void aotContributedInitializerBindsValueObject() {
95+
compile(createContext(ValueObjectSampleBeanConfiguration.class), (freshContext) -> {
96+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "test.name=Hello");
97+
freshContext.refresh();
98+
ValueObjectSampleBean bean = freshContext.getBean(ValueObjectSampleBean.class);
99+
assertThat(bean.name).isEqualTo("Hello");
100+
});
101+
}
102+
103+
@Test
104+
@CompileWithTargetClassAccess
105+
void aotContributedInitializerBindsJavaBean() {
106+
compile(createContext(JavaBeanSampleBeanConfiguration.class), (freshContext) -> {
107+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "test.name=Hello");
108+
freshContext.refresh();
109+
JavaBeanSampleBean bean = freshContext.getBean(JavaBeanSampleBean.class);
110+
assertThat(bean.getName()).isEqualTo("Hello");
111+
});
112+
}
113+
114+
@Test
115+
@CompileWithTargetClassAccess
116+
void aotContributedInitializerBindsScannedValueObject() {
117+
compile(createContext(ScanTestConfiguration.class), (freshContext) -> {
118+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "b.first.name=Hello");
119+
freshContext.refresh();
120+
BFirstProperties bean = freshContext.getBean(BFirstProperties.class);
121+
assertThat(bean.getName()).isEqualTo("Hello");
122+
});
123+
}
124+
125+
@Test
126+
@CompileWithTargetClassAccess
127+
void aotContributedInitializerBindsScannedJavaBean() {
128+
compile(createContext(ScanTestConfiguration.class), (freshContext) -> {
129+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(freshContext, "b.second.number=42");
130+
freshContext.refresh();
131+
BSecondProperties bean = freshContext.getBean(BSecondProperties.class);
132+
assertThat(bean.getNumber()).isEqualTo(42);
133+
});
134+
}
135+
136+
private GenericApplicationContext createContext(Class<?>... types) {
137+
GenericApplicationContext context = new AnnotationConfigApplicationContext();
138+
context.registerBean(JavaBeanSampleBeanConfiguration.class);
139+
Arrays.stream(types).forEach((type) -> context.registerBean(type));
140+
return context;
141+
}
142+
143+
@SuppressWarnings("unchecked")
144+
private void compile(GenericApplicationContext context, Consumer<GenericApplicationContext> freshContext) {
145+
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
146+
ClassName className = new ApplicationContextAotGenerator().generateApplicationContext(context,
147+
generationContext);
148+
generationContext.writeGeneratedContent();
149+
TestCompiler.forSystem().withFiles(generationContext.getGeneratedFiles()).compile((compiled) -> {
150+
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
151+
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
152+
.getInstance(ApplicationContextInitializer.class, className.toString());
153+
initializer.initialize(freshApplicationContext);
154+
freshContext.accept(freshApplicationContext);
155+
});
156+
}
157+
158+
@Configuration(proxyBeanMethods = false)
159+
@EnableConfigurationProperties(JavaBeanSampleBean.class)
160+
static class JavaBeanSampleBeanConfiguration {
161+
162+
}
163+
76164
@ConfigurationProperties("test")
77165
public static class JavaBeanSampleBean {
78166

@@ -88,6 +176,12 @@ public void setName(String name) {
88176

89177
}
90178

179+
@Configuration(proxyBeanMethods = false)
180+
@EnableConfigurationProperties(ValueObjectSampleBean.class)
181+
static class ValueObjectSampleBeanConfiguration {
182+
183+
}
184+
91185
@ConfigurationProperties("test")
92186
public static class ValueObjectSampleBean {
93187

@@ -100,4 +194,14 @@ public static class ValueObjectSampleBean {
100194

101195
}
102196

197+
@Configuration(proxyBeanMethods = false)
198+
@ConfigurationPropertiesScan(basePackageClasses = BScanConfiguration.class)
199+
static class ScanTestConfiguration {
200+
201+
}
202+
203+
static class TestTarget {
204+
205+
}
206+
103207
}

0 commit comments

Comments
 (0)