Spring配置文件

Spring配置加载是Spring框架的核心功能之一,它涉及复杂的机制和流程。以下我会从使用和原理多个角度对Spring的配置文件进行深入介绍。

  • 首先需要明确的是,Spring的所有配置(命令行参数、配置文件、环境变量)都管理在Environment对象中。

  • 不同来源的配置属性被包装为不同的PropertySource对象,而这些PropertySource均被维护在Environment的内部队列(MutuablePropertySources,这玩意本质上也就是个管理PropertySource的List)

  • Environment会处理springboot启动时配置(spring.profiles.active)的active profile,默认会读取application.properties或者application.yml

  • Environment本身只负责找到配置文件,具体文件的解析会分发到对应的parser进行解析(例如yml的parser)

配置加载的核心原理

Spring配置加载的核心在于Environment环境抽象和PropertySource属性源机制。1.1 Environment与PropertySource架构

Spring通过Environment接口抽象了整个应用运行环境,而PropertySource则代表具体的配置属性源。在应用启动时,Spring会按特定顺序将各种配置源封装成PropertySource对象,并添加到Environment的PropertySource队列中。

当应用程序需要获取某个配置属性时,Environment会按照PropertySource队列的顺序从前往后查找,一旦找到就返回,后续的PropertySource将被忽略。这就是高优先级配置覆盖低优先级配置的实现原理。

// 调用environment的getPropert方法的最终实现在PropertySourcesPropertyResolver中
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
          	// List的迭代器,有序
            Iterator var4 = this.propertySources.iterator();

            while(var4.hasNext()) {
                PropertySource<?> propertySource = (PropertySource)var4.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
                }

                Object value = propertySource.getProperty(key);
                if (value != null) { // 找到就返回
                    if (resolveNestedPlaceholders && value instanceof String) {
                        String string = (String)value;
                        value = this.resolveNestedPlaceholders(string);
                    }

                    this.logKeyFound(key, propertySource, value);
                    return this.convertValueIfNecessary(value, targetValueType);
                }
            }
        }

1.2 Spring Boot配置加载详细流程

Spring Boot的配置加载始于SpringApplication.run()方法,具体流程如下:

  1. 初始化Environment:在prepareEnvironment()方法中创建ConfigurableEnvironment对象,用于存储环境参数。

  2. 加载配置源:通过configureEnvironment()方法加载默认的application配置文件和用户指定的配置文件,将其封装为PropertySource并添加到环境对象中。

  3. 处理Profile:根据激活的Profile加载特定的配置(如application-dev.yml)。

  4. 绑定到应用:通过binder将配置属性绑定到Spring应用(依赖于反射,将属性设置到bean中)上。

1.3 配置文件加载顺序与优先级

Spring Boot按照从高到低的优先级顺序加载配置文件,高优先级配置会覆盖低优先级配置。具体位置和顺序如下:

优先级

配置位置

说明

1

file:./config/*/

当前目录下的config文件夹的子目录

2

file:./config/

当前目录下的config文件夹

3

file:./

当前目录(项目根目录)

4

classpath:/config/

classpath下的config包

5

classpath:/

classpath根路径

除了文件配置,Spring Boot还会按特定顺序加载其他配置源,优先级从高到低为:命令行参数、Java系统属性、操作系统环境变量、外部配置文件、内部配置文件等。

1.4 Profile特定配置的加载机制

Spring支持通过spring.profiles.active参数激活不同环境的配置。加载顺序有明确的规则:

  1. 先加载默认的application.yml(或application.properties)

  2. 然后加载application-{active}.yml中独有的配置

  3. 最后用application-{active}.yml中的属性覆盖默认配置文件中相同的属性

设置Profile的方式有多种,其优先级为:命令行参数 > Java系统属性 > 配置文件中的设置

1.5 配置文件解析过程

ConfigFileApplicationListener是Spring Boot加载配置文件的核心类,它监听ApplicationEnvironmentPreparedEvent事件。其内部类Loader负责实际的文件加载工作:

  1. 遍历默认搜索路径(classpath:/, classpath:/config/, file:./, file:./config/)

  2. 查找默认配置文件名(application)

  3. 根据文件扩展名选择合适的PropertySourceLoader(支持properties、xml、yml、yaml格式)

  4. 解析文件内容并封装成PropertySource对象

YAML文件的解析较为特殊,因为支持多文档块(通过---分隔),每个文档块可以对应不同的Profile,这使得可以在一个文件中定义多个环境配置。

配置加载的注意事项

2.1 配置类使用要点

Spring的配置类是定义Bean和配置的核心,使用时需注意:

  • @Configuration注解的类必须由Spring容器管理才会生效,即需要被组件扫描到或显式导入。

  • 配置类中的@Bean方法默认使用单例模式,多次调用返回同一实例。

  • 使用@Profile注解可以条件地创建Bean,只有指定Profile激活时,对应的Bean才会被创建。

2.2 属性注入的多种方式与陷阱

2.2.1 @Value注解的使用与限制

@Value注解虽然简单易用,但存在不少陷阱:

  1. 缺失配置的处理:如果引用不存在的配置,应用启动会报错。解决方案是提供默认值:

    @Value("${app.name:默认应用名}")
    private String appName;
  2. 静态变量注入:@Value不能直接用于静态变量,因为静态变量不属于对象实例,而Spring基于实例进行依赖注入。可以通过setter方法间接实现:

    private static String staticValue;
    
    @Value("${app.value}")
    public void setStaticValue(String value) {
        staticValue = value;
    }
  3. 常量(final)字段:@Value不能直接用于final字段,因为final字段必须在构造函数中初始化。可以通过构造函数注入实现。

  4. 非注册类中的使用:只有在Spring管理的Bean(标注了@Component、@Service等注解的类)中,@Value才会生效。

2.2.2 @ConfigurationProperties的类型安全配置

@ConfigurationProperties提供了一种类型安全的配置方式,本身不创建bean,需要区别于EnableConfigurationProperties注解(真正要求把bean注入)。

configuration_processor依赖支持验证和补全提示(可以理解为一个辅助开发工具,本身对于项目意义不大):

@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    private String name;
    private String version;
    // getters and setters are required
}

在application.yml中配置:

app:
  name: app
  version: 1.0.0

此处,对configuration、enableAutoConfiguration、EnableConfigurationProperties和ConfigurationProperties注解做个对比:

  1. 首先记住,带Properties的是一对,是相互协作的,其工作流如下

    1. User 在 @Configuration 类上写了 @EnableConfigurationProperties(MyProps.class)。

    2. Spring 解析该注解,触发 EnableConfigurationPropertiesRegistrar。

    3. Registrar 检查 MyProps.class 是否有 @ConfigurationProperties 注解。

    4. Registrar 手动向容器注册一个 BeanDefinition(名字通常是 <prefix>-<className>)。

    5. Bean 创建时,ConfigurationPropertiesBindingPostProcessor 拦截该 Bean,读取 Environment 并填充数据

  2. Configuration和EnableAutoConfiguration是另外一对

    1. 这就是自动装配,其调用链如下,不再赘述

    2. AbstractApplicationContext.refresh()
      └-> invokeBeanFactoryPostProcessors()
      └-> ConfigurationClassPostProcessor.processConfigBeanDefinitions()
      ├-> [1] 解析用户定义的 @Configuration (MyApp)
      │ └-> 发现 @Import(AutoConfigurationImportSelector) -> 放入 Deferred 队列

      ├-> [2] 所有configuration解析完毕...

      └-> [3] 处理 DeferredImportSelectorHandler
      └-> AutoConfigurationImportSelector.selectImports()
      ├-> SpringFactoriesLoader/ImportCandidates.load() (读取文件)
      ├-> Filter (基于 @ConditionalOnClass 等过滤)
      └-> 返回符合条件的 Config 类名列表 (e.g., AAutoConfig, BAutoConfig)

└-> [4] CCPP 循环解析这些返回的 AutoConfig 类 -> 注册 @Bean

维度

@Configuration

@EnableAutoConfiguration

@ConfigurationProperties

@EnableConfigurationProperties

核心角色

Blueprint:bean holder

Discovery

Property Holder

Property Activator

主要作用

定义 Bean 的工厂,告诉 Spring 如何创建对象。

告诉 Spring Boot 去 classpath 找“默认蓝图”并批量导入。

声明属性绑定规则(前缀、字段类型)。

将属性模具类注册为 Bean,使其生效。

底层机制

被ConfigurationClassPostProcessor 解析,处理 @Bean 方法。

利用 DeferredImportSelector 机制,延迟加载 spring.factories 或 imports 文件中的配置类。

依赖 Binder API 和反射,将 Environment 值映射到对象。

利用 ImportBeanDefinitionRegistrar 手动注册 Bean Definition。

来源

自定义

一般是入口函数或者配置类,也会读取

META-INF 下的配置文件 + Classpath 环境

application.yml / application.properties。

指向带有 @ConfigurationProperties 的类。

关系/依赖

通常作为其他三个注解的宿主

结果会Import 很多@Configuration 

通常被 @Configuration 类通过 @Enable... 引用。

通常标注在 @Configuration 类上。

一句话总结

“手工配置。”

“自动配置。”

“配置数据。”

“使用配置数据。”

2.2.3 Environment的灵活运用

通过Environment对象可以直接访问配置属性,特别适合在需要编程式获取配置的场景:

@Autowired
private Environment env;

public void someMethod() {
    String value = env.getProperty("app.name");
    String valueWithDefault = env.getProperty("app.name", "default");
}

Environment实现了PropertyResolver接口,提供了丰富的属性解析方法。

2.3 外部化配置最佳实践

  1. 敏感信息管理:永远不要将密码、API密钥等敏感信息直接提交到代码库。使用环境变量、密钥管理服务或外部配置服务器。

  2. 多环境配置:使用Profile-specific配置文件(如application-dev.yml、application-prod.yml)管理不同环境配置。

  3. 自定义配置文件:对于大型项目,可以使用@PropertySource注解加载自定义配置文件:

    @Configuration
    @PropertySource("classpath:custom.properties")
    public class CustomConfig {
        // 配置类
    }

    注意:@PropertySource默认不支持YAML格式,如需加载YAML文件需要额外配置。

  4. 配置优先级管理:理解配置源的优先级,确保关键配置在适当的位置被正确覆盖。

总结

Spring配置加载是一个多层次、有序的过程,理解其原理对于正确使用和 troubleshooting 至关重要。关键要点包括:

  1. 掌握配置源的加载顺序和优先级,这是理解配置覆盖行为的基础。

  2. 合理运用Profile机制实现多环境配置管理。

  3. 根据场景选择合适的配置方式:简单值用@Value,复杂结构化配置用@ConfigurationProperties,编程式获取用Environment。

  4. 避免常见的配置陷阱,如静态变量注入、缺失配置处理等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值