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()方法,具体流程如下:
-
初始化Environment:在prepareEnvironment()方法中创建ConfigurableEnvironment对象,用于存储环境参数。
-
加载配置源:通过configureEnvironment()方法加载默认的application配置文件和用户指定的配置文件,将其封装为PropertySource并添加到环境对象中。
-
处理Profile:根据激活的Profile加载特定的配置(如application-dev.yml)。
-
绑定到应用:通过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参数激活不同环境的配置。加载顺序有明确的规则:
-
先加载默认的application.yml(或application.properties)
-
然后加载application-{active}.yml中独有的配置
-
最后用application-{active}.yml中的属性覆盖默认配置文件中相同的属性
设置Profile的方式有多种,其优先级为:命令行参数 > Java系统属性 > 配置文件中的设置。
1.5 配置文件解析过程
ConfigFileApplicationListener是Spring Boot加载配置文件的核心类,它监听ApplicationEnvironmentPreparedEvent事件。其内部类Loader负责实际的文件加载工作:
-
遍历默认搜索路径(classpath:/, classpath:/config/, file:./, file:./config/)
-
查找默认配置文件名(application)
-
根据文件扩展名选择合适的PropertySourceLoader(支持properties、xml、yml、yaml格式)
-
解析文件内容并封装成PropertySource对象
YAML文件的解析较为特殊,因为支持多文档块(通过---分隔),每个文档块可以对应不同的Profile,这使得可以在一个文件中定义多个环境配置。
配置加载的注意事项
2.1 配置类使用要点
Spring的配置类是定义Bean和配置的核心,使用时需注意:
-
@Configuration注解的类必须由Spring容器管理才会生效,即需要被组件扫描到或显式导入。
-
配置类中的@Bean方法默认使用单例模式,多次调用返回同一实例。
-
使用@Profile注解可以条件地创建Bean,只有指定Profile激活时,对应的Bean才会被创建。
2.2 属性注入的多种方式与陷阱
2.2.1 @Value注解的使用与限制
@Value注解虽然简单易用,但存在不少陷阱:
-
缺失配置的处理:如果引用不存在的配置,应用启动会报错。解决方案是提供默认值:
@Value("${app.name:默认应用名}") private String appName; -
静态变量注入:@Value不能直接用于静态变量,因为静态变量不属于对象实例,而Spring基于实例进行依赖注入。可以通过setter方法间接实现:
private static String staticValue; @Value("${app.value}") public void setStaticValue(String value) { staticValue = value; } -
常量(final)字段:@Value不能直接用于final字段,因为final字段必须在构造函数中初始化。可以通过构造函数注入实现。
-
非注册类中的使用:只有在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注解做个对比:
-
首先记住,带Properties的是一对,是相互协作的,其工作流如下
-
User 在 @Configuration 类上写了 @EnableConfigurationProperties(MyProps.class)。
-
Spring 解析该注解,触发 EnableConfigurationPropertiesRegistrar。
-
Registrar 检查 MyProps.class 是否有 @ConfigurationProperties 注解。
-
Registrar 手动向容器注册一个 BeanDefinition(名字通常是 <prefix>-<className>)。
-
Bean 创建时,ConfigurationPropertiesBindingPostProcessor 拦截该 Bean,读取 Environment 并填充数据
-
-
Configuration和EnableAutoConfiguration是另外一对
-
这就是自动装配,其调用链如下,不再赘述
-
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 外部化配置最佳实践
-
敏感信息管理:永远不要将密码、API密钥等敏感信息直接提交到代码库。使用环境变量、密钥管理服务或外部配置服务器。
-
多环境配置:使用Profile-specific配置文件(如application-dev.yml、application-prod.yml)管理不同环境配置。
-
自定义配置文件:对于大型项目,可以使用@PropertySource注解加载自定义配置文件:
@Configuration @PropertySource("classpath:custom.properties") public class CustomConfig { // 配置类 }注意:@PropertySource默认不支持YAML格式,如需加载YAML文件需要额外配置。
-
配置优先级管理:理解配置源的优先级,确保关键配置在适当的位置被正确覆盖。
总结
Spring配置加载是一个多层次、有序的过程,理解其原理对于正确使用和 troubleshooting 至关重要。关键要点包括:
-
掌握配置源的加载顺序和优先级,这是理解配置覆盖行为的基础。
-
合理运用Profile机制实现多环境配置管理。
-
根据场景选择合适的配置方式:简单值用@Value,复杂结构化配置用@ConfigurationProperties,编程式获取用Environment。
-
避免常见的配置陷阱,如静态变量注入、缺失配置处理等。
4851

被折叠的 条评论
为什么被折叠?



