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:
@@ -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;
|
||||
@@ -40,7 +37,6 @@ import java.util.function.Predicate;
|
||||
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;
|
||||
@@ -59,17 +55,11 @@ import org.springframework.core.Ordered;
|
||||
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;
|
||||
@@ -83,7 +73,6 @@ import org.springframework.util.ClassUtils;
|
||||
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
|
||||
@@ -109,8 +98,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
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."));
|
||||
|
||||
@@ -128,6 +115,9 @@ class ConfigurationClassParser {
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
@Nullable
|
||||
private final PropertySourceRegistry propertySourceRegistry;
|
||||
|
||||
private final BeanDefinitionRegistry registry;
|
||||
|
||||
private final ComponentScanAnnotationParser componentScanParser;
|
||||
@@ -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();
|
||||
@@ -159,6 +147,9 @@ class ConfigurationClassParser {
|
||||
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);
|
||||
@@ -220,6 +211,10 @@ class ConfigurationClassParser {
|
||||
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)) {
|
||||
@@ -275,8 +270,8 @@ class ConfigurationClassParser {
|
||||
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() +
|
||||
@@ -433,85 +428,6 @@ class ConfigurationClassParser {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
@@ -24,6 +26,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
@@ -65,10 +68,13 @@ import org.springframework.context.ResourceLoaderAware;
|
||||
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;
|
||||
@@ -76,11 +82,13 @@ 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
|
||||
@@ -156,6 +164,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
|
||||
|
||||
@Nullable
|
||||
private List<PropertySourceDescriptor> propertySourceDescriptors;
|
||||
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
@@ -285,7 +296,19 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,6 +413,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
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.
|
||||
@@ -505,7 +531,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
}
|
||||
|
||||
|
||||
private static class AotContribution implements BeanFactoryInitializationAotContribution {
|
||||
private static class ImportAwareAotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private static final String BEAN_FACTORY_VARIABLE = BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE;
|
||||
|
||||
@@ -520,7 +546,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
public AotContribution(ConfigurableListableBeanFactory beanFactory) {
|
||||
public ImportAwareAotContribution(ConfigurableListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@@ -585,4 +611,85 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
List<CodeBlock> arguments = new ArrayList<>();
|
||||
arguments.add(CodeBlock.of("$T.of($L)", List.class, values));
|
||||
arguments.add(CodeBlock.of("$L", descriptor.ignoreResourceNotFound()));
|
||||
arguments.add(handleNull(descriptor.name(), () -> CodeBlock.of("$S", descriptor.name())));
|
||||
arguments.add(handleNull(descriptor.propertySourceFactory(),
|
||||
() -> CodeBlock.of("$T.class", descriptor.propertySourceFactory())));
|
||||
arguments.add(handleNull(descriptor.encoding(),
|
||||
() -> CodeBlock.of("$S", descriptor.encoding())));
|
||||
code.add(CodeBlock.join(arguments, ", "));
|
||||
code.add(")");
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private CodeBlock handleNull(@Nullable Object value, Supplier<CodeBlock> nonNull) {
|
||||
if (value == null) {
|
||||
return CodeBlock.of("null");
|
||||
}
|
||||
else {
|
||||
return nonNull.get();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.io.support.PropertySourceDescriptor;
|
||||
import org.springframework.core.io.support.PropertySourceFactory;
|
||||
import org.springframework.core.io.support.PropertySourceProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Registry of {@link PropertySource} processed on configuration classes.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see PropertySourceDescriptor
|
||||
*/
|
||||
class PropertySourceRegistry {
|
||||
|
||||
private final PropertySourceProcessor propertySourceProcessor;
|
||||
|
||||
private final List<PropertySourceDescriptor> descriptors;
|
||||
|
||||
public PropertySourceRegistry(PropertySourceProcessor propertySourceProcessor) {
|
||||
this.propertySourceProcessor = propertySourceProcessor;
|
||||
this.descriptors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<PropertySourceDescriptor> getDescriptors() {
|
||||
return Collections.unmodifiableList(this.descriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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");
|
||||
Class<? extends PropertySourceFactory> factorClassToUse =
|
||||
(factoryClass != PropertySourceFactory.class ? factoryClass : null);
|
||||
PropertySourceDescriptor descriptor = new PropertySourceDescriptor(Arrays.asList(locations), ignoreResourceNotFound, name,
|
||||
factorClassToUse, encoding);
|
||||
this.propertySourceProcessor.processPropertySource(descriptor);
|
||||
this.descriptors.add(descriptor);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user