spring-reading./spring-annotation/spring-annotation-propertyS.../README.md

28 KiB
Raw Blame History

@PropertySource

一、基本信息

✒️ 作者 - Lex 📝 博客 - 我的CSDN 📚 文章目录 - 所有文章 🔗 源码地址 - @PropertySource源码

二、注解描述

@PropertySource 注解,用于指定外部的属性文件,从而将该文件中的键值对加载到 Spring 的 Environment 中。这样,我们就可以在应用程序中轻松地访问和使用这些属性值。

三、注解源码

@Configuration注解是 Spring 框架自 3.1 版本开始引入的一个核心注解,@PropertySource 是用于在 Spring 框架中声明属性源的注解。它允许我们指定外部属性文件(例如 .properties.xml 文件),并将这些文件中的键值对加载到 Spring 的 Environment 中,从而使得应用程序可以轻松访问和使用这些属性值。

/**
 * @PropertySource 是一个用于在 Spring 框架中声明属性源的注解。
 * 使用此注解,我们可以指定外部的属性文件(如 .properties 或 .xml 文件),
 * 从而将该文件中的键值对加载到 Spring 的 Environment 中。
 * 这样,应用程序就可以轻松地访问和使用这些属性值。
 * 
 * 通常,此注解与 @Configuration 注解类一起使用,为整个 Spring 环境提供配置属性。
 * 
 * 示例:
 * Configuration
 * PropertySource("classpath:/com/myco/app.properties")
 * public class AppConfig {
 *     // ...
 * }
 * 
 * 通过上面的配置,应用程序可以确保 `app.properties` 文件中的属性都可以在 Spring Environment 中使用。
 * 
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Phillip Webb
 * @author Sam Brannen
 * @since 3.1
 * @see PropertySources
 * @see Configuration
 * @see org.springframework.core.env.PropertySource
 * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
 * @see org.springframework.core.env.MutablePropertySources
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

    /**
     * 指定此属性源的名称。如果省略,工厂将根据底层资源生成一个名称。
     */
    String name() default "";

    /**
     * 指定要加载的属性文件的资源位置。
     * 支持传统的属性文件和XML格式例如"classpath:/com/myco/app.properties" 或 "file:/path/to/file.xml"。
     * 不支持资源位置的通配符。
     * ${...} 占位符将根据已在 Environment 中注册的所有属性源进行解析。
     * 每个位置将按声明的顺序添加到封闭的 Environment 作为它自己的属性源。
     */
    String[] value();

    /**
     * 指示是否应忽略找不到的属性资源。
     * 如果属性文件是完全可选的,则 true 是合适的。
     * 默认值为 false。
     */
    boolean ignoreResourceNotFound() default false;

    /**
     * 为给定的资源指定一个特定的字符编码,例如 "UTF-8"。
     */
    String encoding() default "";

    /**
     * 指定一个自定义的 PropertySourceFactory。
     * 默认情况下,将使用标准资源文件的默认工厂。
     */
    Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

四、主要功能

  1. 加载属性文件
    • 使我们能够指定一个或多个外部属性文件(如 .properties.xml 文件),并将其加载到 Spring 的上下文中。
  2. 声明资源位置
    • @PropertySource 提供一个 value 属性,用于指定外部属性文件的位置,如 classpath:/com/myco/app.propertiesfile:/path/to/file.xml
  3. 属性覆盖
    • 当使用多个 @PropertySource 注解或 PropertySources 注解(该注解允许我们指定多个 @PropertySource)时,后声明的属性源会覆盖先声明的属性源中的同名属性。
  4. 处理资源找不到的情况
    • 通过 ignoreResourceNotFound 属性,我们可以指定当资源找不到时是否抛出异常。这在某些属性文件是可选的情况下特别有用。
  5. 指定文件编码
    • 通过 encoding 属性,我们可以为资源文件指定特定的字符编码,如 "UTF-8"。
  6. 自定义属性源工厂
    • 使用 factory 属性,我们可以为属性源指定一个自定义的 PropertySourceFactory,用于解析和加载属性。
  7. 支持占位符解析
    • @PropertySource 指定的文件中的属性值可以使用 ${...} 占位符,这些占位符将被解析为在 Environment 中已经加载的其他属性的值。
  8. Environment 交互
    • 一旦属性文件被加载到 Environment 中,可以通过 @Autowired 注入 Environment 来查询和使用这些属性。

五、最佳实践

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个MyConfiguration组件类。然后从中获取两个属性:apiVersionkind

public class PropertySourceApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        System.out.println("apiVersion = " + context.getEnvironment().getProperty("apiVersion"));
        System.out.println("kind = " + context.getEnvironment().getProperty("kind"));
    }
}

MyConfiguration 类标注了 @Configuration这意味着它是一个基于Java的Spring配置类。同时它还使用 @PropertySource 注解来加载名为 my-application.yml 的外部属性文件。

@Configuration
@PropertySource("classpath:my-application.yml")
public class MyConfiguration {

}

my-application.yml 文件包含以下内容

apiVersion: v1
kind: ConfigMap

运行结果发现我们从Spring应用成功地从一个YAML文件中加载了属性并能够在应用中使用这些属性值。

apiVersion = v1
kind = ConfigMap

六、时序图

sequenceDiagram
Title: @PropertySource注解时序图

ComponentScanApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)<br>创建上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext: refresh()<br>刷新上下文
AbstractApplicationContext->>AbstractApplicationContext: invokeBeanFactoryPostProcessors(beanFactory)<br>调用Bean工厂后置处理器
AbstractApplicationContext->>PostProcessorRegistrationDelegate: invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)<br>委托处理
PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate: invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup) <br>注册后置处理器
PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor: postProcessBeanDefinitionRegistry(registry)<br>配置类后处理
ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor: processConfigBeanDefinitions(registry)<br>处理Bean定义
ConfigurationClassPostProcessor->>ConfigurationClassParser: ConfigurationClassParser(...)<br>创建解析器
ConfigurationClassParser-->>ConfigurationClassPostProcessor: 返回解析器<br>
ConfigurationClassPostProcessor->>ConfigurationClassParser: parser.parse(candidates)<br>解析候选项
ConfigurationClassParser->>ConfigurationClassParser: parse(metadata, String beanName)<br>解析元数据
ConfigurationClassParser->>ConfigurationClassParser: processConfigurationClass(configClass,filter)<br>处理配置类
ConfigurationClassParser->>ConfigurationClassParser: doProcessConfigurationClass(configClass,sourceClass,filter)<br>执行处理操作
ConfigurationClassParser->>ConfigurationClassParser: processPropertySource(propertySource)<br>处理属性源
ConfigurationClassParser->>DefaultPropertySourceFactory:createPropertySource(name,resource)<br>创建属性源
DefaultPropertySourceFactory->>ConfigurationClassParser:返回PropertySource
ConfigurationClassParser->>StandardEnvironment:getPropertySources()<br>获取属性源
StandardEnvironment->>ConfigurationClassParser:返回MutablePropertySources
ConfigurationClassParser->>ConfigurationClassParser: addPropertySource(propertySource)<br>添加属性源
ConfigurationClassParser->>MutablePropertySources:addLast(propertySource)<br>添加最低优先级的属性源

七、源码分析

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个MyConfiguration组件类。然后从中获取两个属性:apiVersionkind

public class PropertySourceApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        System.out.println("apiVersion = " + context.getEnvironment().getProperty("apiVersion"));
        System.out.println("kind = " + context.getEnvironment().getProperty("kind"));
    }
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext构造函数中,执行了三个步骤,我们重点关注refresh()方法。

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

org.springframework.context.support.AbstractApplicationContext#refresh方法中我们重点关注一下finishBeanFactoryInitialization(beanFactory)这方法会对实例化所有剩余非懒加载的单列Bean对象其他方法不是本次源码阅读的重点暂时忽略。

@Override
public void refresh() throws BeansException, IllegalStateException {
     // ... [代码部分省略以简化]
     // 调用在上下文中注册为bean的工厂处理器
     invokeBeanFactoryPostProcessors(beanFactory);
     // ... [代码部分省略以简化]
}

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中,又委托了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()进行调用。

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    // ... [代码部分省略以简化]
}

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中,首先调用了 BeanDefinitionRegistryPostProcessor(这是 BeanFactoryPostProcessor 的子接口)。它专门用来在所有其他 bean 定义加载之前修改默认的 bean 定义。

public static void invokeBeanFactoryPostProcessors(
        ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    // ... [代码部分省略以简化]
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
	// ... [代码部分省略以简化]
}

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors方法中,循环调用了实现BeanDefinitionRegistryPostProcessor接口中的postProcessBeanDefinitionRegistry(registry)方法

private static void invokeBeanDefinitionRegistryPostProcessors(
			Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
            .tag("postProcessor", postProcessor::toString);
        postProcessor.postProcessBeanDefinitionRegistry(registry);
        postProcessBeanDefRegistry.end();
    }
}

org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中,调用了processConfigBeanDefinitions方法该方法的主要目的是处理和注册配置类中定义的beans。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // ... [代码部分省略以简化]
    processConfigBeanDefinitions(registry);
}

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中这个方法主要处理了配置类的解析和验证并确保了所有在配置类中定义的beans都被正确地注册到Spring的bean定义注册表中。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // ... [代码部分省略以简化]

    // 步骤1创建一个用于解析配置类的解析器
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    // 步骤2初始化候选配置类集合以及已解析配置类集合
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());

    // 步骤3循环处理所有候选配置类直至没有候选类为止
    do {
        // 步骤3.1 解析配置类
        parser.parse(candidates);
        
        // 步骤3.2 验证配置类
        parser.validate();
        
        // ... [代码部分省略以简化]
        
    } while (!candidates.isEmpty());

    // ... [代码部分省略以简化]
}

org.springframework.context.annotation.ConfigurationClassParser#parse方法中主要是遍历所有的配置类候选者并对每一个带有注解的Bean定义进行解析。这通常涉及到查找该配置类中的@Bean方法、组件扫描指令等并将这些信息注册到Spring容器中。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    // ... [代码部分省略以简化]
    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    // ... [代码部分省略以简化]
}

org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)方法中将注解元数据和Bean名称转化为一个配置类然后对其进行处理。处理配置类是Spring配置驱动的核心它涉及到许多关键操作如处理@ComponentScan注解等等。

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass方法中,处理一个给定的配置类。它首先递归地处理配置类及其父类,以确保所有相关的配置都被正确地读取并解析。在递归处理完所有相关配置后,它将配置类添加到已解析的配置类的映射中。

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    // ... [代码部分省略以简化]

    // 步骤1递归地处理配置类及其超类层次结构
    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    } while (sourceClass != null);

    // 步骤2将处理后的配置类放入映射中
    this.configurationClasses.put(configClass, configClass);
}

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中,主要任务是处理 ConfigurationClass 中定义的 @PropertySource 注解,并确保它们被正确地添加到 ConfigurableEnvironment。如果环境不可配置(即不是 ConfigurableEnvironment 的实例),则会忽略该注解并记录相关信息。

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

    // 处理任何 @PropertySource 注解
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
        
        // 检查当前环境是否为ConfigurableEnvironment的实例
        if (this.environment instanceof ConfigurableEnvironment) {
            // 如果是,则处理该属性源
            processPropertySource(propertySource);
        } 
        else {
            // 否则,记录一条信息,说明正在忽略@PropertySource注解
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    // 没有超类 -> 处理完成
    return null;
}

org.springframework.context.annotation.ConfigurationClassParser#processPropertySource方法中,首先解析 @PropertySource 注解的各个属性,如 nameencodingvaluefactory。然后它尝试加载每个指定的属性文件并根据文件的内容和其他相关属性创建一个属性源最后将这个属性源添加到Spring环境中。

/**
 * 处理给定的@PropertySource注解属性。
 *
 * @param propertySource @PropertySource注解的属性
 * @throws IOException 如果处理时发生IO异常
 */
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
    
    // 获取"name"属性如果没有指定设置为null
    String name = propertySource.getString("name");
    if (!StringUtils.hasLength(name)) {
        name = null;
    }

    // 获取"encoding"属性如果没有指定设置为null
    String encoding = propertySource.getString("encoding");
    if (!StringUtils.hasLength(encoding)) {
        encoding = null;
    }

    // 获取"value"属性,它表示属性文件的位置
    String[] locations = propertySource.getStringArray("value");
    // 确保至少指定了一个位置
    Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");

    // 获取"ignoreResourceNotFound"属性,表示如果资源找不到是否应该忽略
    boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

    // 获取并实例化PropertySourceFactory用于创建属性源
    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) {
            // 当尝试打开资源时,占位符不可解析或资源找不到
            if (ignoreResourceNotFound) {
                // 如果忽略资源找不到,则记录一条信息
                if (logger.isInfoEnabled()) {
                    logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
                }
            }
            else {
                // 否则,抛出异常
                throw ex;
            }
        }
    }
}

org.springframework.core.io.support.DefaultPropertySourceFactory#createPropertySource方法中,如果提供了名称,它将为该属性源使用该名称。如果没有提供名称,它将只基于资源创建一个属性源,属性源的名称可能会基于资源自己的描述或其他默认方式来确定。

@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
    return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}

org.springframework.context.annotation.ConfigurationClassParser#addPropertySource方法中,首先检查是否已经添加了具有相同名称的属性源。如果是,则它会将新的属性源与现有的属性源合并。如果没有,它将按照适当的顺序添加新的属性源。

private void addPropertySource(PropertySource<?> propertySource) {
    // 获取属性源的名称
    String name = propertySource.getName();
    // 获取当前环境的所有属性源
    MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();

    // 如果此名称的属性源已经被添加过
    if (this.propertySourceNames.contains(name)) {
        // 我们已经添加了一个版本,需要扩展它
        PropertySource<?> existing = propertySources.get(name);
        if (existing != null) {
            PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
                                           ((ResourcePropertySource) propertySource).withResourceName() : propertySource);
            // 如果现有的属性源是CompositePropertySource
            if (existing instanceof CompositePropertySource) {
                ((CompositePropertySource) existing).addFirstPropertySource(newSource);
            }
            else {
                // 创建一个新的CompositePropertySource并将新的和现有的属性源添加进去
                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);
}

八、注意事项

  1. 文件位置
    • 确保我们提供的文件路径是正确的。例如,classpath: 前缀表示文件应该在类路径中,而 file: 前缀则表示文件应该在文件系统的特定位置。
  2. 占位符
    • @PropertySourcevalue 属性中,我们可以使用 ${...} 占位符,它们将会被已注册的任何属性源解析。
  3. 处理重复的属性源名称
    • 如果我们有多个 @PropertySource 注解(或使用 @PropertySources 注解)且它们具有相同的名称,那么它们会合并。后声明的 @PropertySource 将覆盖先前声明的同名 @PropertySource
  4. 属性源的顺序
    • 属性源的顺序很重要,因为在多个属性源中定义的同名属性将使用先找到的值。我们可以使用 PropertySourcename 属性来明确指定属性源的名称,以控制其在环境中的顺序。
  5. 忽略找不到的资源
    • 我们可以使用 ignoreResourceNotFound 属性来指定当属性文件找不到时是否应该抛出异常。默认情况下,这是 false,意味着如果属性文件找不到,会抛出异常。设置为 true 可以让Spring在找不到文件时安静地继续运行。
  6. 字符编码
    • 从Spring 4.3开始,@PropertySource 注解有一个 encoding 属性,允许我们为给定的资源指定特定的字符编码。
  7. 自定义属性源工厂
    • 如果我们需要特殊的逻辑来创建属性源,可以使用 factory 属性来指定一个自定义的 PropertySourceFactory
  8. 激活属性占位符解析
    • 仅仅使用 @PropertySource 并不会激活属性占位符解析。为了替换我们的bean定义中的 ${...} 占位符,我们还需要添加 @Bean 定义为 PropertySourcesPlaceholderConfigurer
  9. 与Profiles结合
    • 我们可以与Spring的Profile功能结合使用 @PropertySource,以根据不同的环境加载不同的属性文件。

九、总结

最佳实践总结

  1. 初始化上下文
    • 使用 AnnotationConfigApplicationContext 作为Spring容器的初始化方式它允许从Java注解中配置Spring容器。
  2. 定义配置类
    • 创建一个名为 MyConfiguration 的类,并使用 @Configuration 注解来标记它表示这是一个基于Java的Spring配置类。
  3. 指定属性源
    • MyConfiguration 类中,使用 @PropertySource 注解指定了一个外部属性文件的位置,文件名为 my-application.yml
  4. 定义属性内容
    • my-application.yml 文件中定义了两个属性:apiVersionkind
  5. 加载并访问属性
    • 运行应用程序后使用Spring的 Environment API来访问这些属性并将其输出到控制台。
  6. 运行结果
    • 从YAML文件成功加载的属性在控制台上显示为 apiVersion = v1kind = ConfigMap

源码分析总结

  1. 上下文初始化
    • 通过使用 AnnotationConfigApplicationContext, Spring 上下文被初始化。该类允许Spring容器从Java注解中进行配置。传递的配置类是 MyConfiguration,该类定义了要从中加载的属性源。
  2. 处理@PropertySource注解
    • 在配置类处理期间Spring 检查每个配置类以查找 @PropertySource 注解。如果找到,则属性源的相关数据(例如其位置和其他属性)被提取出来。
  3. 资源位置解析
    • 对于 @PropertySource 注解中定义的每个属性文件位置Spring 尝试加载和解析该文件。它首先解析任何占位符,然后尝试加载资源。
  4. 创建属性源
    • 使用 DefaultPropertySourceFactory, Spring 创建一个属性源。这个工厂可以从给定的资源(例如 .properties 或 .yml 文件)创建一个属性源。
  5. 向环境中添加属性源
    • 一旦属性源被创建它就被添加到Spring的运行时环境中。如果已经存在具有相同名称的属性源新的属性源可能会与旧的合并或者会以适当的方式进行处理。
  6. 处理完成
    • 在完成所有配置类和属性源的处理之后Spring上下文继续其正常的启动过程最终在应用程序运行时提供这些属性。