Skip to content

Commit 7642ac8

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. Closes gh-28976
1 parent fcb6baf commit 7642ac8

File tree

6 files changed

+644
-236
lines changed

6 files changed

+644
-236
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: 110 additions & 3 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;
@@ -24,6 +26,7 @@
2426
import java.util.List;
2527
import java.util.Map;
2628
import java.util.Set;
29+
import java.util.function.Supplier;
2730

2831
import javax.lang.model.element.Modifier;
2932

@@ -65,22 +68,27 @@
6568
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
6669
import org.springframework.core.Ordered;
6770
import org.springframework.core.PriorityOrdered;
71+
import org.springframework.core.env.ConfigurableEnvironment;
6872
import org.springframework.core.env.Environment;
6973
import org.springframework.core.env.StandardEnvironment;
7074
import org.springframework.core.io.DefaultResourceLoader;
7175
import org.springframework.core.io.ResourceLoader;
76+
import org.springframework.core.io.support.PropertySourceDescriptor;
77+
import org.springframework.core.io.support.PropertySourceProcessor;
7278
import org.springframework.core.metrics.ApplicationStartup;
7379
import org.springframework.core.metrics.StartupStep;
7480
import org.springframework.core.type.AnnotationMetadata;
7581
import org.springframework.core.type.MethodMetadata;
7682
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
7783
import org.springframework.core.type.classreading.MetadataReaderFactory;
7884
import org.springframework.javapoet.CodeBlock;
85+
import org.springframework.javapoet.CodeBlock.Builder;
7986
import org.springframework.javapoet.MethodSpec;
8087
import org.springframework.javapoet.ParameterizedTypeName;
8188
import org.springframework.lang.Nullable;
8289
import org.springframework.util.Assert;
8390
import org.springframework.util.ClassUtils;
91+
import org.springframework.util.CollectionUtils;
8492

8593
/**
8694
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
@@ -156,6 +164,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
156164

157165
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
158166

167+
@Nullable
168+
private List<PropertySourceDescriptor> propertySourceDescriptors;
169+
159170

160171
@Override
161172
public int getOrder() {
@@ -285,7 +296,19 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
285296

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

291314
/**
@@ -390,6 +413,9 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
390413
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
391414
}
392415

416+
// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
417+
this.propertySourceDescriptors = parser.getPropertySourceDescriptors();
418+
393419
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cachingMetadataReaderFactory) {
394420
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
395421
// for a shared cache since it'll be cleared by the ApplicationContext.
@@ -505,7 +531,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) {
505531
}
506532

507533

508-
private static class AotContribution implements BeanFactoryInitializationAotContribution {
534+
private static class ImportAwareAotContribution implements BeanFactoryInitializationAotContribution {
509535

510536
private static final String BEAN_FACTORY_VARIABLE = BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE;
511537

@@ -520,7 +546,7 @@ private static class AotContribution implements BeanFactoryInitializationAotCont
520546

521547
private final ConfigurableListableBeanFactory beanFactory;
522548

523-
public AotContribution(ConfigurableListableBeanFactory beanFactory) {
549+
public ImportAwareAotContribution(ConfigurableListableBeanFactory beanFactory) {
524550
this.beanFactory = beanFactory;
525551
}
526552

@@ -585,4 +611,85 @@ private Map<String, String> buildImportAwareMappings() {
585611

586612
}
587613

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

0 commit comments

Comments
 (0)