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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.core.io.support;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Describe a {@link PropertySource}.
|
||||
*
|
||||
* @param locations the locations to consider
|
||||
* @param ignoreResourceNotFound whether to fail if a location does not exist
|
||||
* @param name the name of the property source, or {@code null} to infer one
|
||||
* @param propertySourceFactory the {@link PropertySourceFactory} to use, or
|
||||
* {@code null} to use the default
|
||||
* @param encoding the encoding, or {@code null} to use the default encoding
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public record PropertySourceDescriptor(List<String> locations, boolean ignoreResourceNotFound,
|
||||
@Nullable String name, @Nullable Class<? extends PropertySourceFactory> propertySourceFactory,
|
||||
@Nullable String encoding) {
|
||||
|
||||
/**
|
||||
* Create a descriptor with the specified locations.
|
||||
* @param locations the locations to consider
|
||||
*/
|
||||
public PropertySourceDescriptor(String... locations) {
|
||||
this(Arrays.asList(locations), false, null, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.core.io.support;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
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.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Contribute {@link PropertySource property sources} to the {@link Environment}.
|
||||
*
|
||||
* <p>This class is stateful and merge descriptors with the same name in a
|
||||
* single {@link PropertySource} rather than creating dedicated ones.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see PropertySourceDescriptor
|
||||
*/
|
||||
public class PropertySourceProcessor {
|
||||
|
||||
private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PropertySourceProcessor.class);
|
||||
|
||||
private final ConfigurableEnvironment environment;
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private final List<String> propertySourceNames;
|
||||
|
||||
public PropertySourceProcessor(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
|
||||
this.environment = environment;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.propertySourceNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the specified {@link PropertySourceDescriptor} against the
|
||||
* environment managed by this instance.
|
||||
* @param descriptor the descriptor to process
|
||||
* @throws IOException if loading the properties failed
|
||||
*/
|
||||
public void processPropertySource(PropertySourceDescriptor descriptor) throws IOException {
|
||||
String name = descriptor.name();
|
||||
String encoding = descriptor.encoding();
|
||||
List<String> locations = descriptor.locations();
|
||||
Assert.isTrue(locations.size() > 0, "At least one @PropertySource(value) location is required");
|
||||
boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound();
|
||||
PropertySourceFactory factory = (descriptor.propertySourceFactory() != null
|
||||
? instantiateClass(descriptor.propertySourceFactory())
|
||||
: DEFAULT_PROPERTY_SOURCE_FACTORY);
|
||||
|
||||
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(org.springframework.core.env.PropertySource<?> propertySource) {
|
||||
String name = propertySource.getName();
|
||||
MutablePropertySources propertySources = this.environment.getPropertySources();
|
||||
|
||||
if (this.propertySourceNames.contains(name)) {
|
||||
// We've already added a version, we need to extend it
|
||||
org.springframework.core.env.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);
|
||||
}
|
||||
|
||||
private PropertySourceFactory instantiateClass(Class<? extends PropertySourceFactory> type) {
|
||||
try {
|
||||
Constructor<? extends PropertySourceFactory> constructor = type.getDeclaredConstructor();
|
||||
ReflectionUtils.makeAccessible(constructor);
|
||||
return constructor.newInstance();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to instantiate " + type, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user