Skip to content

Commit 1a4fa86

Browse files
committed
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. See spring-projectsgh-28976
1 parent 4075556 commit 1a4fa86

File tree

7 files changed

+611
-237
lines changed

7 files changed

+611
-237
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

Lines changed: 14 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import java.io.FileNotFoundException;
2019
import java.io.IOException;
2120
import java.lang.annotation.Annotation;
22-
import java.net.SocketException;
23-
import java.net.UnknownHostException;
2421
import java.util.ArrayDeque;
2522
import java.util.ArrayList;
2623
import java.util.Collection;
@@ -40,7 +37,6 @@
4037
import org.apache.commons.logging.Log;
4138
import org.apache.commons.logging.LogFactory;
4239

43-
import org.springframework.beans.BeanUtils;
4440
import org.springframework.beans.factory.BeanDefinitionStoreException;
4541
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
4642
import org.springframework.beans.factory.config.BeanDefinition;
@@ -59,17 +55,11 @@
5955
import org.springframework.core.annotation.AnnotationAttributes;
6056
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
6157
import org.springframework.core.annotation.AnnotationUtils;
62-
import org.springframework.core.env.CompositePropertySource;
6358
import org.springframework.core.env.ConfigurableEnvironment;
6459
import org.springframework.core.env.Environment;
65-
import org.springframework.core.env.MutablePropertySources;
66-
import org.springframework.core.env.PropertySource;
67-
import org.springframework.core.io.Resource;
6860
import org.springframework.core.io.ResourceLoader;
69-
import org.springframework.core.io.support.DefaultPropertySourceFactory;
70-
import org.springframework.core.io.support.EncodedResource;
71-
import org.springframework.core.io.support.PropertySourceFactory;
72-
import org.springframework.core.io.support.ResourcePropertySource;
61+
import org.springframework.core.io.support.PropertySourceDescriptor;
62+
import org.springframework.core.io.support.PropertySourceProcessor;
7363
import org.springframework.core.type.AnnotationMetadata;
7464
import org.springframework.core.type.MethodMetadata;
7565
import org.springframework.core.type.StandardAnnotationMetadata;
@@ -83,7 +73,6 @@
8373
import org.springframework.util.CollectionUtils;
8474
import org.springframework.util.LinkedMultiValueMap;
8575
import org.springframework.util.MultiValueMap;
86-
import org.springframework.util.StringUtils;
8776

8877
/**
8978
* Parses a {@link Configuration} class definition, populating a collection of
@@ -109,8 +98,6 @@
10998
*/
11099
class ConfigurationClassParser {
111100

112-
private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
113-
114101
private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = className ->
115102
(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));
116103

@@ -128,6 +115,9 @@ class ConfigurationClassParser {
128115

129116
private final ResourceLoader resourceLoader;
130117

118+
@Nullable
119+
private final PropertySourceRegistry propertySourceRegistry;
120+
131121
private final BeanDefinitionRegistry registry;
132122

133123
private final ComponentScanAnnotationParser componentScanParser;
@@ -138,8 +128,6 @@ class ConfigurationClassParser {
138128

139129
private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<>();
140130

141-
private final List<String> propertySourceNames = new ArrayList<>();
142-
143131
private final ImportStack importStack = new ImportStack();
144132

145133
private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();
@@ -159,6 +147,9 @@ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
159147
this.problemReporter = problemReporter;
160148
this.environment = environment;
161149
this.resourceLoader = resourceLoader;
150+
this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce
151+
? new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader))
152+
: null);
162153
this.registry = registry;
163154
this.componentScanParser = new ComponentScanAnnotationParser(
164155
environment, resourceLoader, componentScanBeanNameGenerator, registry);
@@ -220,6 +211,10 @@ public Set<ConfigurationClass> getConfigurationClasses() {
220211
return this.configurationClasses.keySet();
221212
}
222213

214+
List<PropertySourceDescriptor> getPropertySourceDescriptors() {
215+
return (this.propertySourceRegistry != null ? this.propertySourceRegistry.getDescriptors()
216+
: Collections.emptyList());
217+
}
223218

224219
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
225220
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
@@ -275,8 +270,8 @@ protected final SourceClass doProcessConfigurationClass(
275270
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
276271
sourceClass.getMetadata(), PropertySources.class,
277272
org.springframework.context.annotation.PropertySource.class)) {
278-
if (this.environment instanceof ConfigurableEnvironment) {
279-
processPropertySource(propertySource);
273+
if (this.propertySourceRegistry != null) {
274+
this.propertySourceRegistry.processPropertySource(propertySource);
280275
}
281276
else {
282277
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
@@ -433,85 +428,6 @@ private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass)
433428
}
434429

435430

436-
/**
437-
* Process the given <code>@PropertySource</code> annotation metadata.
438-
* @param propertySource metadata for the <code>@PropertySource</code> annotation found
439-
* @throws IOException if loading a property source failed
440-
*/
441-
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
442-
String name = propertySource.getString("name");
443-
if (!StringUtils.hasLength(name)) {
444-
name = null;
445-
}
446-
String encoding = propertySource.getString("encoding");
447-
if (!StringUtils.hasLength(encoding)) {
448-
encoding = null;
449-
}
450-
String[] locations = propertySource.getStringArray("value");
451-
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
452-
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
453-
454-
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
455-
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
456-
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
457-
458-
for (String location : locations) {
459-
try {
460-
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
461-
Resource resource = this.resourceLoader.getResource(resolvedLocation);
462-
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
463-
}
464-
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
465-
// Placeholders not resolvable or resource not found when trying to open it
466-
if (ignoreResourceNotFound) {
467-
if (logger.isInfoEnabled()) {
468-
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
469-
}
470-
}
471-
else {
472-
throw ex;
473-
}
474-
}
475-
}
476-
}
477-
478-
private void addPropertySource(PropertySource<?> propertySource) {
479-
String name = propertySource.getName();
480-
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
481-
482-
if (this.propertySourceNames.contains(name)) {
483-
// We've already added a version, we need to extend it
484-
PropertySource<?> existing = propertySources.get(name);
485-
if (existing != null) {
486-
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
487-
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
488-
if (existing instanceof CompositePropertySource) {
489-
((CompositePropertySource) existing).addFirstPropertySource(newSource);
490-
}
491-
else {
492-
if (existing instanceof ResourcePropertySource) {
493-
existing = ((ResourcePropertySource) existing).withResourceName();
494-
}
495-
CompositePropertySource composite = new CompositePropertySource(name);
496-
composite.addPropertySource(newSource);
497-
composite.addPropertySource(existing);
498-
propertySources.replace(name, composite);
499-
}
500-
return;
501-
}
502-
}
503-
504-
if (this.propertySourceNames.isEmpty()) {
505-
propertySources.addLast(propertySource);
506-
}
507-
else {
508-
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
509-
propertySources.addBefore(firstProcessed, propertySource);
510-
}
511-
this.propertySourceNames.add(name);
512-
}
513-
514-
515431
/**
516432
* Returns {@code @Import} class, considering all meta-annotations.
517433
*/

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
1921
import java.util.ArrayList;
2022
import java.util.HashMap;
2123
import java.util.HashSet;
@@ -65,22 +67,27 @@
6567
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
6668
import org.springframework.core.Ordered;
6769
import org.springframework.core.PriorityOrdered;
70+
import org.springframework.core.env.ConfigurableEnvironment;
6871
import org.springframework.core.env.Environment;
6972
import org.springframework.core.env.StandardEnvironment;
7073
import org.springframework.core.io.DefaultResourceLoader;
7174
import org.springframework.core.io.ResourceLoader;
75+
import org.springframework.core.io.support.PropertySourceDescriptor;
76+
import org.springframework.core.io.support.PropertySourceProcessor;
7277
import org.springframework.core.metrics.ApplicationStartup;
7378
import org.springframework.core.metrics.StartupStep;
7479
import org.springframework.core.type.AnnotationMetadata;
7580
import org.springframework.core.type.MethodMetadata;
7681
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
7782
import org.springframework.core.type.classreading.MetadataReaderFactory;
7883
import org.springframework.javapoet.CodeBlock;
84+
import org.springframework.javapoet.CodeBlock.Builder;
7985
import org.springframework.javapoet.MethodSpec;
8086
import org.springframework.javapoet.ParameterizedTypeName;
8187
import org.springframework.lang.Nullable;
8288
import org.springframework.util.Assert;
8389
import org.springframework.util.ClassUtils;
90+
import org.springframework.util.CollectionUtils;
8491

8592
/**
8693
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
@@ -156,6 +163,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
156163

157164
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
158165

166+
@Nullable
167+
private List<PropertySourceDescriptor> propertySourceDescriptors;
168+
159169

160170
@Override
161171
public int getOrder() {
@@ -285,7 +295,19 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
285295

286296
@Override
287297
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
288-
return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME) ? new AotContribution(beanFactory) : null);
298+
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors);
299+
boolean hasImportRegistry = beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME);
300+
if (hasPropertySourceDescriptors || hasImportRegistry) {
301+
return (generationContext, code) -> {
302+
if (hasPropertySourceDescriptors) {
303+
new PropertySourcesAotContribution(this.propertySourceDescriptors).applyTo(generationContext, code);
304+
}
305+
if (hasImportRegistry) {
306+
new ImportAwareAotContribution(beanFactory).applyTo(generationContext, code);
307+
}
308+
};
309+
}
310+
return null;
289311
}
290312

291313
/**
@@ -390,6 +412,9 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
390412
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
391413
}
392414

415+
// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
416+
this.propertySourceDescriptors = parser.getPropertySourceDescriptors();
417+
393418
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cachingMetadataReaderFactory) {
394419
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
395420
// for a shared cache since it'll be cleared by the ApplicationContext.
@@ -422,7 +447,7 @@ public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFact
422447
// or component class without @Bean methods.
423448
boolean liteConfigurationCandidateWithoutBeanMethods =
424449
(ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
425-
annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
450+
annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
426451
if (!liteConfigurationCandidateWithoutBeanMethods) {
427452
try {
428453
abd.resolveBeanClass(this.beanClassLoader);
@@ -505,7 +530,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) {
505530
}
506531

507532

508-
private static class AotContribution implements BeanFactoryInitializationAotContribution {
533+
private static class ImportAwareAotContribution implements BeanFactoryInitializationAotContribution {
509534

510535
private static final String BEAN_FACTORY_VARIABLE = BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE;
511536

@@ -520,7 +545,7 @@ private static class AotContribution implements BeanFactoryInitializationAotCont
520545

521546
private final ConfigurableListableBeanFactory beanFactory;
522547

523-
public AotContribution(ConfigurableListableBeanFactory beanFactory) {
548+
public ImportAwareAotContribution(ConfigurableListableBeanFactory beanFactory) {
524549
this.beanFactory = beanFactory;
525550
}
526551

@@ -585,4 +610,82 @@ private Map<String, String> buildImportAwareMappings() {
585610

586611
}
587612

613+
private static class PropertySourcesAotContribution implements BeanFactoryInitializationAotContribution {
614+
615+
private static final String ENVIRONMENT_VARIABLE = "environment";
616+
617+
private static final String RESOURCE_LOADER_VARIABLE = "resourceLoader";
618+
619+
private final List<PropertySourceDescriptor> descriptors;
620+
621+
PropertySourcesAotContribution(List<PropertySourceDescriptor> descriptors) {
622+
this.descriptors = descriptors;
623+
}
624+
625+
@Override
626+
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
627+
GeneratedMethod generatedMethod = beanFactoryInitializationCode
628+
.getMethods()
629+
.add("processPropertySources", this::generateAddPropertySourceProcessorMethod);
630+
beanFactoryInitializationCode
631+
.addInitializer(generatedMethod.toMethodReference());
632+
}
633+
634+
private void generateAddPropertySourceProcessorMethod(MethodSpec.Builder method) {
635+
method.addJavadoc("Apply known @PropertySources to the environment.");
636+
method.addModifiers(Modifier.PRIVATE);
637+
method.addParameter(ConfigurableEnvironment.class, ENVIRONMENT_VARIABLE);
638+
method.addParameter(ResourceLoader.class, RESOURCE_LOADER_VARIABLE);
639+
method.addCode(generateAddPropertySourceProcessorCode());
640+
}
641+
642+
private CodeBlock generateAddPropertySourceProcessorCode() {
643+
Builder code = CodeBlock.builder();
644+
String processorVariable = "processor";
645+
code.addStatement("$T $L = new $T($L, $L)", PropertySourceProcessor.class,
646+
processorVariable, PropertySourceProcessor.class, ENVIRONMENT_VARIABLE,
647+
RESOURCE_LOADER_VARIABLE);
648+
code.beginControlFlow("try");
649+
for (PropertySourceDescriptor descriptor : this.descriptors) {
650+
code.addStatement("$L.processPropertySource($L)", processorVariable,
651+
generatePropertySourceDescriptorCode(descriptor));
652+
}
653+
code.nextControlFlow("catch ($T ex)", IOException.class);
654+
code.addStatement("throw new $T(ex)", UncheckedIOException.class);
655+
code.endControlFlow();
656+
return code.build();
657+
}
658+
659+
private CodeBlock generatePropertySourceDescriptorCode(PropertySourceDescriptor descriptor) {
660+
CodeBlock.Builder code = CodeBlock.builder();
661+
code.add("new $T(", PropertySourceDescriptor.class);
662+
CodeBlock values = descriptor.locations().stream()
663+
.map(value -> CodeBlock.of("$S", value)).collect(CodeBlock.joining(", "));
664+
if (descriptor.name() == null && descriptor.propertySourceFactory() == null
665+
&& descriptor.encoding() == null && !descriptor.ignoreResourceNotFound()) {
666+
code.add("$L)", values);
667+
}
668+
else {
669+
code.add("$T.of($L), ", List.class, values);
670+
handleNull(code, descriptor.name(), () -> code.add("$S, ", descriptor.name()));
671+
handleNull(code, descriptor.propertySourceFactory(),
672+
() -> code.add("$T.class, ", descriptor.propertySourceFactory()));
673+
handleNull(code, descriptor.encoding(), () -> code.add("$S, ", descriptor.encoding()));
674+
code.add("$L", descriptor.ignoreResourceNotFound());
675+
code.add(")");
676+
}
677+
return code.build();
678+
}
679+
680+
private void handleNull(CodeBlock.Builder code, @Nullable Object value, Runnable nonNull) {
681+
if (value == null) {
682+
code.add("null");
683+
}
684+
else {
685+
nonNull.run();
686+
}
687+
}
688+
689+
}
690+
588691
}

0 commit comments

Comments
 (0)