Apply @PropertySource in AOT-generated context
This commit records `@PropertySource` declarations defined on configuration classes so that these are contributed to the environment of a context that is initialized by generated code. Closes gh-28976
This commit is contained in:
@@ -24,6 +24,7 @@ import java.util.function.Consumer;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
@@ -46,6 +47,8 @@ import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.testfixture.context.generator.annotation.ImportAwareConfiguration;
|
||||
import org.springframework.context.testfixture.context.generator.annotation.ImportConfiguration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
@@ -70,84 +73,262 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
new MockBeanFactoryInitializationCode(this.generationContext);
|
||||
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenNoImportAwareConfigurationReturnsNull() {
|
||||
assertThat(getContribution(SimpleConfiguration.class)).isNull();
|
||||
@Nested
|
||||
class ImportAwareTests {
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenNoImportAwareConfigurationReturnsNull() {
|
||||
assertThat(getContribution(SimpleConfiguration.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersBeanPostProcessorWithMapEntry() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(ImportConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory freshBeanFactory = freshContext.getDefaultListableBeanFactory();
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshContext.refresh();
|
||||
assertThat(freshBeanFactory.getBeanPostProcessors()).filteredOn(ImportAwareAotBeanPostProcessor.class::isInstance)
|
||||
.singleElement().satisfies(postProcessor -> assertPostProcessorEntry(postProcessor, ImportAwareConfiguration.class,
|
||||
ImportConfiguration.class));
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersBeanPostProcessorAfterApplicationContextAwareProcessor() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(TestAwareCallbackConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory freshBeanFactory = freshContext.getDefaultListableBeanFactory();
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshContext.registerBean(TestAwareCallbackBean.class);
|
||||
freshContext.refresh();
|
||||
TestAwareCallbackBean bean = freshContext.getBean(TestAwareCallbackBean.class);
|
||||
assertThat(bean.instances).hasSize(2);
|
||||
assertThat(bean.instances.get(0)).isEqualTo(freshContext);
|
||||
assertThat(bean.instances.get(1)).isInstanceOfSatisfying(AnnotationMetadata.class, metadata ->
|
||||
assertThat(metadata.getClassName()).isEqualTo(TestAwareCallbackConfiguration.class.getName()));
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersBeanPostProcessorBeforeRegularBeanPostProcessor() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(
|
||||
TestImportAwareBeanPostProcessorConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory freshBeanFactory = freshContext.getDefaultListableBeanFactory();
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshBeanFactory.registerBeanDefinition(TestImportAwareBeanPostProcessor.class.getName(),
|
||||
new RootBeanDefinition(TestImportAwareBeanPostProcessor.class));
|
||||
RootBeanDefinition bd = new RootBeanDefinition(String.class);
|
||||
bd.setInstanceSupplier(() -> "test");
|
||||
freshBeanFactory.registerBeanDefinition("testProcessing", bd);
|
||||
freshContext.refresh();
|
||||
assertThat(freshContext.getBean("testProcessing")).isInstanceOfSatisfying(AnnotationMetadata.class, metadata ->
|
||||
assertThat(metadata.getClassName()).isEqualTo(TestImportAwareBeanPostProcessorConfiguration.class.getName())
|
||||
);
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersHints() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(ImportConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
assertThat(generationContext.getRuntimeHints().resources().resourcePatterns())
|
||||
.singleElement()
|
||||
.satisfies(resourceHint -> assertThat(resourceHint.getIncludes())
|
||||
.map(ResourcePatternHint::getPattern)
|
||||
.containsOnly("org/springframework/context/testfixture/context/generator/annotation/"
|
||||
+ "ImportConfiguration.class"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(BiConsumer<Consumer<DefaultListableBeanFactory>, Compiled> result) {
|
||||
MethodReference methodReference = beanFactoryInitializationCode.getInitializers().get(0);
|
||||
beanFactoryInitializationCode.getTypeBuilder().set(type -> {
|
||||
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(
|
||||
ArgumentCodeGenerator.of(DefaultListableBeanFactory.class, "beanFactory"),
|
||||
beanFactoryInitializationCode.getClassName());
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, DefaultListableBeanFactory.class));
|
||||
type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(DefaultListableBeanFactory.class, "beanFactory")
|
||||
.addStatement(methodInvocation)
|
||||
.build());
|
||||
});
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().withFiles(generationContext.getGeneratedFiles()).compile(compiled ->
|
||||
result.accept(compiled.getInstance(Consumer.class), compiled));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(TestAwareCallbackBean.class)
|
||||
static class TestAwareCallbackConfiguration {
|
||||
}
|
||||
|
||||
static class TestAwareCallbackBean implements ImportAware, ApplicationContextAware {
|
||||
|
||||
private final List<Object> instances = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.instances.add(applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
this.instances.add(importMetadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(TestImportAwareBeanPostProcessor.class)
|
||||
static class TestImportAwareBeanPostProcessorConfiguration {
|
||||
}
|
||||
|
||||
static class TestImportAwareBeanPostProcessor implements BeanPostProcessor, ImportAware,
|
||||
Ordered, InitializingBean {
|
||||
|
||||
private AnnotationMetadata metadata;
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
this.metadata = importMetadata;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (beanName.equals("testProcessing")) {
|
||||
return this.metadata;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(this.metadata, "Metadata was not injected");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersBeanPostProcessorWithMapEntry() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(ImportConfiguration.class);
|
||||
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory freshBeanFactory = freshContext.getDefaultListableBeanFactory();
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshContext.refresh();
|
||||
assertThat(freshBeanFactory.getBeanPostProcessors()).filteredOn(ImportAwareAotBeanPostProcessor.class::isInstance)
|
||||
.singleElement().satisfies(postProcessor -> assertPostProcessorEntry(postProcessor, ImportAwareConfiguration.class,
|
||||
ImportConfiguration.class));
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
@Nested
|
||||
class PropertySourceTests {
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersBeanPostProcessorAfterApplicationContextAwareProcessor() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(TestAwareCallbackConfiguration.class);
|
||||
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory freshBeanFactory = freshContext.getDefaultListableBeanFactory();
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshContext.registerBean(TestAwareCallbackBean.class);
|
||||
freshContext.refresh();
|
||||
TestAwareCallbackBean bean = freshContext.getBean(TestAwareCallbackBean.class);
|
||||
assertThat(bean.instances).hasSize(2);
|
||||
assertThat(bean.instances.get(0)).isEqualTo(freshContext);
|
||||
assertThat(bean.instances.get(1)).isInstanceOfSatisfying(AnnotationMetadata.class, metadata ->
|
||||
assertThat(metadata.getClassName()).isEqualTo(TestAwareCallbackConfiguration.class.getName()));
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
@Test
|
||||
void applyToWhenHasPropertySourceInvokePropertySourceProcessor() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(
|
||||
PropertySourceConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
ConfigurableEnvironment environment = freshContext.getEnvironment();
|
||||
assertThat(environment.containsProperty("from.p1")).isFalse();
|
||||
initializer.accept(freshContext);
|
||||
assertThat(environment.containsProperty("from.p1")).isTrue();
|
||||
assertThat(environment.getProperty("from.p1")).isEqualTo("p1Value");
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersBeanPostProcessorBeforeRegularBeanPostProcessor() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(
|
||||
TestImportAwareBeanPostProcessorConfiguration.class);
|
||||
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory freshBeanFactory = freshContext.getDefaultListableBeanFactory();
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshBeanFactory.registerBeanDefinition(TestImportAwareBeanPostProcessor.class.getName(),
|
||||
new RootBeanDefinition(TestImportAwareBeanPostProcessor.class));
|
||||
RootBeanDefinition bd = new RootBeanDefinition(String.class);
|
||||
bd.setInstanceSupplier(() -> "test");
|
||||
freshBeanFactory.registerBeanDefinition("testProcessing", bd);
|
||||
freshContext.refresh();
|
||||
assertThat(freshContext.getBean("testProcessing")).isInstanceOfSatisfying(AnnotationMetadata.class, metadata ->
|
||||
assertThat(metadata.getClassName()).isEqualTo(TestImportAwareBeanPostProcessorConfiguration.class.getName())
|
||||
);
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
@Test
|
||||
void applyToWhenHasPropertySourcesInvokesPropertySourceProcessorInOrder() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(
|
||||
PropertySourceConfiguration.class, PropertySourceDependentConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
ConfigurableEnvironment environment = freshContext.getEnvironment();
|
||||
assertThat(environment.containsProperty("from.p1")).isFalse();
|
||||
assertThat(environment.containsProperty("from.p2")).isFalse();
|
||||
initializer.accept(freshContext);
|
||||
assertThat(environment.containsProperty("from.p1")).isTrue();
|
||||
assertThat(environment.getProperty("from.p1")).isEqualTo("p1Value");
|
||||
assertThat(environment.containsProperty("from.p2")).isTrue();
|
||||
assertThat(environment.getProperty("from.p2")).isEqualTo("p2Value");
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasPropertySourceWithDetailsRetainsThem() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(
|
||||
PropertySourceWithDetailsConfiguration.class);
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
ConfigurableEnvironment environment = freshContext.getEnvironment();
|
||||
assertThat(environment.getPropertySources().get("testp1")).isNull();
|
||||
initializer.accept(freshContext);
|
||||
assertThat(environment.getPropertySources().get("testp1")).isNotNull();
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(BiConsumer<Consumer<GenericApplicationContext>, Compiled> result) {
|
||||
MethodReference methodReference = beanFactoryInitializationCode.getInitializers().get(0);
|
||||
beanFactoryInitializationCode.getTypeBuilder().set(type -> {
|
||||
ArgumentCodeGenerator argCodeGenerator = ArgumentCodeGenerator
|
||||
.of(ConfigurableEnvironment.class, "applicationContext.getEnvironment()")
|
||||
.and(ArgumentCodeGenerator.of(ResourceLoader.class, "applicationContext"));
|
||||
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(argCodeGenerator,
|
||||
beanFactoryInitializationCode.getClassName());
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, GenericApplicationContext.class));
|
||||
type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(GenericApplicationContext.class, "applicationContext")
|
||||
.addStatement(methodInvocation)
|
||||
.build());
|
||||
});
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().withFiles(generationContext.getGeneratedFiles()).compile(compiled ->
|
||||
result.accept(compiled.getInstance(Consumer.class), compiled));
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
|
||||
static class PropertySourceConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@PropertySource("classpath:${base.package}/p2.properties")
|
||||
static class PropertySourceDependentConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@PropertySource(name = "testp1", value = "classpath:org/springframework/context/annotation/p1.properties",
|
||||
ignoreResourceNotFound = true)
|
||||
static class PropertySourceWithDetailsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasImportAwareConfigurationRegistersHints() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(ImportConfiguration.class);
|
||||
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
|
||||
assertThat(generationContext.getRuntimeHints().resources().resourcePatterns())
|
||||
.singleElement()
|
||||
.satisfies(resourceHint -> assertThat(resourceHint.getIncludes())
|
||||
.map(ResourcePatternHint::getPattern)
|
||||
.containsOnly("org/springframework/context/testfixture/context/generator/annotation/"
|
||||
+ "ImportConfiguration.class"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanFactoryInitializationAotContribution getContribution(Class<?> type) {
|
||||
private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition("configuration", new RootBeanDefinition(type));
|
||||
for (Class<?> type : types) {
|
||||
beanFactory.registerBeanDefinition(type.getName(), new RootBeanDefinition(type));
|
||||
}
|
||||
ConfigurationClassPostProcessor postProcessor = new ConfigurationClassPostProcessor();
|
||||
postProcessor.postProcessBeanFactory(beanFactory);
|
||||
return postProcessor.processAheadOfTime(beanFactory);
|
||||
@@ -159,81 +340,4 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
.containsExactly(entry(key.getName(), value.getName()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(BiConsumer<Consumer<DefaultListableBeanFactory>, Compiled> result) {
|
||||
MethodReference methodReference = this.beanFactoryInitializationCode.getInitializers().get(0);
|
||||
this.beanFactoryInitializationCode.getTypeBuilder().set(type -> {
|
||||
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(
|
||||
ArgumentCodeGenerator.of(DefaultListableBeanFactory.class, "beanFactory"),
|
||||
this.beanFactoryInitializationCode.getClassName());
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, DefaultListableBeanFactory.class));
|
||||
type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(DefaultListableBeanFactory.class, "beanFactory")
|
||||
.addStatement(methodInvocation)
|
||||
.build());
|
||||
});
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().withFiles(this.generationContext.getGeneratedFiles()).compile(compiled ->
|
||||
result.accept(compiled.getInstance(Consumer.class), compiled));
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(TestAwareCallbackBean.class)
|
||||
static class TestAwareCallbackConfiguration {
|
||||
}
|
||||
|
||||
static class TestAwareCallbackBean implements ImportAware, ApplicationContextAware {
|
||||
|
||||
private final List<Object> instances = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.instances.add(applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
this.instances.add(importMetadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(TestImportAwareBeanPostProcessor.class)
|
||||
static class TestImportAwareBeanPostProcessorConfiguration {
|
||||
}
|
||||
|
||||
static class TestImportAwareBeanPostProcessor implements BeanPostProcessor, ImportAware,
|
||||
Ordered, InitializingBean {
|
||||
|
||||
private AnnotationMetadata metadata;
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
this.metadata = importMetadata;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (beanName.equals("testProcessing")) {
|
||||
return this.metadata;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(this.metadata, "Metadata was not injected");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user