Skip to content

Commit

Permalink
Apply @propertysource in AOT-generated context
Browse files Browse the repository at this point in the history
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
  • Loading branch information
snicoll committed Sep 7, 2022
1 parent 4075556 commit 1a4fa86
Show file tree
Hide file tree
Showing 7 changed files with 611 additions and 237 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@

package org.springframework.context.annotation;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -40,7 +37,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
Expand All @@ -59,17 +55,11 @@
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.io.support.PropertySourceProcessor;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
Expand All @@ -83,7 +73,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

/**
* Parses a {@link Configuration} class definition, populating a collection of
Expand All @@ -109,8 +98,6 @@
*/
class ConfigurationClassParser {

private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();

private static final Predicate<String> DEFAULT_EXCLUSION_FILTER = className ->
(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));

Expand All @@ -128,6 +115,9 @@ class ConfigurationClassParser {

private final ResourceLoader resourceLoader;

@Nullable
private final PropertySourceRegistry propertySourceRegistry;

private final BeanDefinitionRegistry registry;

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

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

private final List<String> propertySourceNames = new ArrayList<>();

private final ImportStack importStack = new ImportStack();

private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();
Expand All @@ -159,6 +147,9 @@ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
this.problemReporter = problemReporter;
this.environment = environment;
this.resourceLoader = resourceLoader;
this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce
? new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader))
: null);
this.registry = registry;
this.componentScanParser = new ComponentScanAnnotationParser(
environment, resourceLoader, componentScanBeanNameGenerator, registry);
Expand Down Expand Up @@ -220,6 +211,10 @@ public Set<ConfigurationClass> getConfigurationClasses() {
return this.configurationClasses.keySet();
}

List<PropertySourceDescriptor> getPropertySourceDescriptors() {
return (this.propertySourceRegistry != null ? this.propertySourceRegistry.getDescriptors()
: Collections.emptyList());
}

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


/**
* Process the given <code>@PropertySource</code> annotation metadata.
* @param propertySource metadata for the <code>@PropertySource</code> annotation found
* @throws IOException if loading a property source failed
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}

private void addPropertySource(PropertySource<?> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();

if (this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
PropertySource<?> existing = propertySources.get(name);
if (existing != null) {
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}

if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}


/**
* Returns {@code @Import} class, considering all meta-annotations.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.context.annotation;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -65,22 +67,27 @@
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.io.support.PropertySourceProcessor;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.StartupStep;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

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

private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;

@Nullable
private List<PropertySourceDescriptor> propertySourceDescriptors;


@Override
public int getOrder() {
Expand Down Expand Up @@ -285,7 +295,19 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME) ? new AotContribution(beanFactory) : null);
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors);
boolean hasImportRegistry = beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME);
if (hasPropertySourceDescriptors || hasImportRegistry) {
return (generationContext, code) -> {
if (hasPropertySourceDescriptors) {
new PropertySourcesAotContribution(this.propertySourceDescriptors).applyTo(generationContext, code);
}
if (hasImportRegistry) {
new ImportAwareAotContribution(beanFactory).applyTo(generationContext, code);
}
};
}
return null;
}

/**
Expand Down Expand Up @@ -390,6 +412,9 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
this.propertySourceDescriptors = parser.getPropertySourceDescriptors();

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
Expand Down Expand Up @@ -422,7 +447,7 @@ public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFact
// or component class without @Bean methods.
boolean liteConfigurationCandidateWithoutBeanMethods =
(ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
if (!liteConfigurationCandidateWithoutBeanMethods) {
try {
abd.resolveBeanClass(this.beanClassLoader);
Expand Down Expand Up @@ -505,7 +530,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) {
}


private static class AotContribution implements BeanFactoryInitializationAotContribution {
private static class ImportAwareAotContribution implements BeanFactoryInitializationAotContribution {

private static final String BEAN_FACTORY_VARIABLE = BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE;

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

private final ConfigurableListableBeanFactory beanFactory;

public AotContribution(ConfigurableListableBeanFactory beanFactory) {
public ImportAwareAotContribution(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

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

}

private static class PropertySourcesAotContribution implements BeanFactoryInitializationAotContribution {

private static final String ENVIRONMENT_VARIABLE = "environment";

private static final String RESOURCE_LOADER_VARIABLE = "resourceLoader";

private final List<PropertySourceDescriptor> descriptors;

PropertySourcesAotContribution(List<PropertySourceDescriptor> descriptors) {
this.descriptors = descriptors;
}

@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
GeneratedMethod generatedMethod = beanFactoryInitializationCode
.getMethods()
.add("processPropertySources", this::generateAddPropertySourceProcessorMethod);
beanFactoryInitializationCode
.addInitializer(generatedMethod.toMethodReference());
}

private void generateAddPropertySourceProcessorMethod(MethodSpec.Builder method) {
method.addJavadoc("Apply known @PropertySources to the environment.");
method.addModifiers(Modifier.PRIVATE);
method.addParameter(ConfigurableEnvironment.class, ENVIRONMENT_VARIABLE);
method.addParameter(ResourceLoader.class, RESOURCE_LOADER_VARIABLE);
method.addCode(generateAddPropertySourceProcessorCode());
}

private CodeBlock generateAddPropertySourceProcessorCode() {
Builder code = CodeBlock.builder();
String processorVariable = "processor";
code.addStatement("$T $L = new $T($L, $L)", PropertySourceProcessor.class,
processorVariable, PropertySourceProcessor.class, ENVIRONMENT_VARIABLE,
RESOURCE_LOADER_VARIABLE);
code.beginControlFlow("try");
for (PropertySourceDescriptor descriptor : this.descriptors) {
code.addStatement("$L.processPropertySource($L)", processorVariable,
generatePropertySourceDescriptorCode(descriptor));
}
code.nextControlFlow("catch ($T ex)", IOException.class);
code.addStatement("throw new $T(ex)", UncheckedIOException.class);
code.endControlFlow();
return code.build();
}

private CodeBlock generatePropertySourceDescriptorCode(PropertySourceDescriptor descriptor) {
CodeBlock.Builder code = CodeBlock.builder();
code.add("new $T(", PropertySourceDescriptor.class);
CodeBlock values = descriptor.locations().stream()
.map(value -> CodeBlock.of("$S", value)).collect(CodeBlock.joining(", "));
if (descriptor.name() == null && descriptor.propertySourceFactory() == null
&& descriptor.encoding() == null && !descriptor.ignoreResourceNotFound()) {
code.add("$L)", values);
}
else {
code.add("$T.of($L), ", List.class, values);
handleNull(code, descriptor.name(), () -> code.add("$S, ", descriptor.name()));
handleNull(code, descriptor.propertySourceFactory(),
() -> code.add("$T.class, ", descriptor.propertySourceFactory()));
handleNull(code, descriptor.encoding(), () -> code.add("$S, ", descriptor.encoding()));
code.add("$L", descriptor.ignoreResourceNotFound());
code.add(")");
}
return code.build();
}

private void handleNull(CodeBlock.Builder code, @Nullable Object value, Runnable nonNull) {
if (value == null) {
code.add("null");
}
else {
nonNull.run();
}
}

}

}
Loading

0 comments on commit 1a4fa86

Please sign in to comment.