外部化配置优先级
概述
Spring Boot 提供了极其丰富的外部化配置机制,允许开发者在不修改代码的前提下,通过多种途径向应用注入配置。理解这些配置源的优先级关系、加载顺序和底层实现,是生产环境中正确管理配置的关键。
配置优先级(17 个来源)
Spring Boot 定义了一个严格的优先级顺序,高优先级的配置会覆盖低优先级的同名配置:
| 优先级 | 配置源 | 示例 |
|---|---|---|
| 1 | 命令行参数 | --server.port=8080 |
| 2 | JNDI 属性 | java:comp/env/ |
| 3 | JVM 系统属性 | -Dserver.port=8080 |
| 4 | OS 环境变量 | SERVER_PORT=8080 |
| 5 | RandomValuePropertySource | random.int |
| 6 | application-{profile}.{ext}(jar 包外) | config/application-dev.yml |
| 7 | application-{profile}.{ext}(jar 包内) | classpath:application-dev.yml |
| 8 | application.{ext}(jar 包外) | config/application.yml |
| 9 | application.{ext}(jar 包内) | classpath:application.yml |
| 10 | @PropertySource 注解 | @PropertySource("classpath:db.properties") |
| 11 | SpringApplication.setDefaultProperties | 编程设置的默认属性 |
| 12 | @TestPropertySource(测试环境) | @TestPropertySource("test.properties") |
| 13 | DevTools 全局设置 | ~/.config/spring-boot/devtools.properties |
| 14 | ServletContext 初始化参数 | web.xml 中的 <context-param> |
| 15 | ServletConfig 初始化参数 | web.xml 中的 <init-param> |
| 16 | spring.config.import 导入的配置源 | 从配置中心引入 |
| 17 | @SpringBootConfiguration 中的默认值 | 代码中的 @Bean 默认属性 |
注意:第 16 项
spring.config.import导入的配置源其内部优先级取决于导入的具体实现(如 Consul、Nacos 等配置中心的回退策略)。
PropertySource 链的构建源码
Environment 接口体系
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
MutablePropertySources getPropertySources();
void merge(ConfigurableEnvironment parent);
}MutablePropertySources 内部维护了一个 CopyOnWriteArrayList<PropertySource<?>>,每个 PropertySource 都有 name 属性和 getProperty(String key) 方法。
构建流程:prepareEnvironment
// SpringApplication.java
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext,
ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(bootstrapContext, environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (args != null && args.length > 0) {
if (this.addCommandLinePropertySource) {
sources.addFirst(new SimpleCommandLinePropertySource("commandLineArgs", args));
}
}
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
DefaultPropertiesPropertySource.addTo(sources, this.defaultProperties);
}
}PropertySource 链结构
┌───────────────────────────────────────────────────────────┐
│ "commandLineArgs" SimpleCommandLinePropertySource │ ← 最高
├───────────────────────────────────────────────────────────┤
│ "servletConfigInitParams" ServletContextPropertySource │
├───────────────────────────────────────────────────────────┤
│ "servletContextInitParams" ServletContextPropertySource │
├───────────────────────────────────────────────────────────┤
│ "systemProperties" PropertiesPropertySource │
├───────────────────────────────────────────────────────────┤
│ "systemEnvironment" SystemEnvironmentPropertySource │
├───────────────────────────────────────────────────────────┤
│ "random" RandomValuePropertySource │
├───────────────────────────────────────────────────────────┤
│ "application-config-[...]" OriginTrackedMapPropertySource │
├───────────────────────────────────────────────────────────┤
│ "defaultProperties" DefaultPropertiesPropertySource│ ← 最低
└───────────────────────────────────────────────────────────┘命令行参数(--xxx=yyy)的解析机制
SimpleCommandLinePropertySource
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
public SimpleCommandLinePropertySource(String name, String[] args) {
super(name, new CommandLineArgs());
parseArgs(args);
}
private void parseArgs(String[] args) {
for (String arg : args) {
if (arg.startsWith("--")) {
String optionText = arg.substring(2);
int indexOfEquals = optionText.indexOf('=');
String optionName, optionValue;
if (indexOfEquals > -1) {
optionName = optionText.substring(0, indexOfEquals);
optionValue = optionText.substring(indexOfEquals + 1);
} else {
optionName = optionText;
optionValue = "true"; // 无 = 号视为布尔 true
}
this.source.addOptionArg(optionName, optionValue);
// 重复 key 会追加到 List 中:--key=a --key=b → key=[a, b]
}
}
}
}CommandLineArgs 数据结构
public class CommandLineArgs {
private final Map<String, List<String>> optionArgs = new HashMap<>();
private final List<String> nonOptionArgs = new ArrayList<>();
public boolean containsOption(String name) {
return this.optionArgs.containsKey(name);
}
@Nullable
public List<String> getOptionValues(String name) {
return this.optionArgs.get(name);
}
}处理流程
main(String[] args)
└─ SpringApplication.run(args)
├─ new DefaultApplicationArguments(args)
└─ configurePropertySources(env, args)
└─ sources.addFirst(new SimpleCommandLinePropertySource("commandLineArgs", args))即使 application.yml 中配置了 server.port: 8080,命令行参数也会覆盖它:
java -jar app.jar --server.port=9090
# → server.port 最终值为 9090环境变量与系统属性的映射规则
SystemEnvironmentPropertySource
Spring Boot 使用 SystemEnvironmentPropertySource 处理 OS 环境变量,核心能力是宽松的键查找:
public class SystemEnvironmentPropertySource extends MapPropertySource {
@Override
@Nullable
public Object getProperty(String name) {
Object value = super.getProperty(name);
if (value != null) return value; // 1. 精确匹配
String relaxedName = resolveRelaxedName(name);
value = super.getProperty(relaxedName); // 2. 宽松规则匹配
if (value != null) return value;
String underscoreName = name.replace('.', '_'); // 3. 点号→下划线
if (!name.equals(underscoreName)) {
value = super.getProperty(underscoreName);
}
if (value != null) return value;
String upperName = underscoreName.toUpperCase(Locale.ENGLISH); // 4. 全大写
if (!underscoreName.equals(upperName)) {
value = super.getProperty(upperName);
}
return value;
}
}命名转换示例
| 配置属性 key | 环境变量形式 | 系统属性形式 |
|---|---|---|
server.port | SERVER_PORT | server.port |
spring.datasource.url | SPRING_DATASOURCE_URL | spring.datasource.url |
myapp.cors.allowed-origins | MYAPP_CORS_ALLOWED_ORIGINS | myapp.cors.allowed-origins |
my.host-name | MY_HOST_NAME | my.host-name |
my.hostName | MY_HOSTNAME | my.hostName |
转换规则
输入 key: spring.datasource.url
查找顺序:
1. "spring.datasource.url" → 精确匹配
2. "spring.datasource.url" → 宽松规则解析
3. "spring_datasource_url" → 点号替换为下划线
4. "SPRING_DATASOURCE_URL" → 全大写 + 下划线(最终形态)系统属性的优先级
systemProperties(-D 参数)优先于 systemEnvironment(OS 环境变量):
// StandardEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
getSystemProperties())); // ← 优先
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
getSystemEnvironment())); // ← 次之
}因此 -Dserver.port=8081 的优先级高于 SERVER_PORT=8080。
YAML 多文档块(--- 分隔)的加载与解析
多文档 YAML 语法
spring:
application:
name: my-app
server:
port: 8080
---
spring:
config:
activate:
on-profile: dev
server:
port: 8081
---
spring:
config:
activate:
on-profile: prod
server:
port: 8080
logging:
level:
root: WARN加载与解析源码
// YamlPropertySourceLoader.java
public class YamlPropertySourceLoader implements PropertySourceLoader {
@Override
public PropertySource<?> load(String name, Resource resource) throws IOException {
if (!isYamlFile(resource)) return null;
List<Map<String, Object>> documents = new Yaml().loadAll(resource.getInputStream());
if (documents.isEmpty()) return null;
if (documents.size() == 1) {
return new OriginTrackedMapPropertySource(name,
Collections.unmodifiableMap(getFlattenedMap(documents.get(0))));
}
List<Map<String, Object>> activeDocuments = new ArrayList<>();
List<Map<String, Object>> inActiveDocuments = new ArrayList<>();
for (Map<String, Object> document : documents) {
if (document.containsKey("spring.config.activate.on-profile")) {
String profile = document.get("spring.config.activate.on-profile").toString();
if (this.activeProfiles.contains(profile)) {
activeDocuments.add(document);
} else {
inActiveDocuments.add(document);
}
} else {
activeDocuments.add(document); // 无 profile 的文档始终加载
}
}
activeDocuments.addAll(inActiveDocuments);
return new OriginTrackedMapPropertySource(name,
Collections.unmodifiableMap(mergeDocuments(activeDocuments)));
}
}加载规则
┌──────────────────────────────────────┐
│ 文档块 1(无 profile) │ ← 始终加载(基础配置)
├──────────────────────────────────────┤
│ 文档块 2(on-profile: dev) │ ← active=dev 时加载,覆盖文档块 1
├──────────────────────────────────────┤
│ 文档块 3(on-profile: prod) │ ← active=prod 时加载,覆盖文档块 1
└──────────────────────────────────────┘@ConfigurationProperties 类型安全绑定的源码实现
入口
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName;
// getter / setter
}
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration { }Binder 核心绑定器
// Binder.java(Spring Boot 2.0+ 的核心绑定机制)
public class Binder {
public <T> T bind(String name, Bindable<T> target) {
String[] names = PropertyNamePatterns.toNames(name);
for (String propertyName : names) {
ConfigurationProperty property = findProperty(propertyName);
if (property != null) {
return bindProperty(property, target);
}
}
return null;
}
}绑定流程
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
ConfigurationProperties annotation = getAnnotation(bean, beanName);
if (annotation == null) return bean;
Binder binder = Binder.get(this.environment);
Bindable<Object> bindable = Bindable.ofInstance(bean)
.withAnnotations(annotation);
binder.bind(annotation.prefix(), bindable);
return bean;
}
}嵌套绑定
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String host = "localhost";
private int port = 6379;
private Sentinel sentinel = new Sentinel();
private Cluster cluster = new Cluster();
public static class Sentinel {
private String master;
private List<String> nodes;
}
public static class Cluster {
private List<String> nodes;
private int maxRedirects = 5;
}
}spring:
redis:
host: 192.168.1.100
sentinel:
master: mymaster
nodes:
- 192.168.1.101:26379
- 192.168.1.102:26379类型转换
Binder 内部使用 ApplicationConversionService,支持丰富的自动类型转换:
public class ApplicationConversionService extends FormattingConversionService {
public ApplicationConversionService() {
addConverter(new StringToDurationConverter()); // "10s" → Duration
addConverter(new StringToDataSizeConverter()); // "10MB" → DataSize
addConverter(new StringToPeriodConverter()); // "2d" → Period
addConverter(new StringToInetAddressConverter()); // "192.168.1.1" → InetAddress
addConverter(new DelimitedStringToArrayConverter()); // "a,b,c" → String[]
addConverter(new StringToCharsetConverter()); // "UTF-8" → Charset
}
}宽松绑定(Relaxed Binding)的命名转换规则
支持的命名风格
# application.yml(短横线、驼峰等价)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
driverClassName: com.mysql.cj.jdbc.Driver # 等价# application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.driver_class_name=com.mysql.cj.jdbc.Driver# 环境变量
SPRING_DATASOURCE_DRIVERCLASSNAME=com.mysql.cj.jdbc.Driver
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver命名转换规则表
| 目标字段名 | 允许的绑定形式 |
|---|---|
driverClassName | driver-class-name、driverClassName、driver_class_name、DRIVER_CLASS_NAME |
myUrl | my-url、myUrl、my_url、MY_URL |
maxPoolSize | max-pool-size、maxPoolSize、max_pool_size、MAX_POOL_SIZE |
corsAllowedOrigins | cors-allowed-origins、corsAllowedOrigins、cors_allowed_origins |
源码实现
Spring Boot 2.x 使用 RelaxedNames 枚举所有变体:
final class RelaxedNames implements Iterable<String> {
private final Set<String> values = new LinkedHashSet<>();
RelaxedNames(String name) { initialize(name); }
private void initialize(String name) {
addName(name); // driverClassName
addName(camelToHyphen(name)); // driver-class-name
addName(camelToUnderscore(name)); // driver_class_name
addName(hyphenToCamel(name)); // driverClassName
addName(underscoreToCamel(name)); // driverClassName
addName(name.toLowerCase()); // driverclassname
addName(name.toUpperCase()); // DRIVERCLASSNAME
}
}Spring Boot 3.x 改用 NamePatternPropertyMapper 按需匹配,避免全量枚举的性能开销。
多环境配置管理最佳实践
文件搜索顺序
1. classpath:/application-{profile}.yml — jar 包内
2. classpath:/config/application-{profile}.yml — jar 内 config 子目录
3. file:./application-{profile}.yml — jar 包同级目录
4. file:./config/application-{profile}.yml — jar 同级 config 子目录
5. file:./config/*/application-{profile}.yml — config 下的子目录建议的项目结构
src/main/resources/
├── application.yml ← 通用配置(所有环境共享)
├── application-dev.yml ← 开发环境
├── application-staging.yml ← 预发布环境
└── application-prod.yml ← 生产环境示例配置
# application.yml — 通用配置
spring:
application:
name: my-app
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
server:
servlet:
context-path: /api# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/myapp_dev
username: dev_user
password: dev_password
jpa:
show-sql: true
hibernate:
ddl-auto: update
server:
port: 8080
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/myapp_prod
username: prod_user
password: ${DB_PASSWORD} # 环境变量注入
jpa:
hibernate:
ddl-auto: validate
config:
import: optional:configserver:http://config-server:8888
server:
port: 80激活方式
# 命令行参数(推荐)
java -jar app.jar --spring.profiles.active=prod
# 环境变量
export SPRING_PROFILES_ACTIVE=prod && java -jar app.jar
# 系统属性
java -Dspring.profiles.active=prod -jar app.jar
# Docker
docker run -e SPRING_PROFILES_ACTIVE=prod my-appProfile 分组(Spring Boot 3.x)
spring:
profiles:
group:
dev: "dev,embedded-mq,embedded-db"
staging: "staging,rabbitmq,mysql"
prod: "prod,rabbitmq,mysql,monitoring"
---
spring:
config:
activate:
on-profile: embedded-mq
---
spring:
config:
activate:
on-profile: rabbitmq启动时 --spring.profiles.active=dev 自动激活 dev、embedded-mq、embedded-db 三个 profile。
加密敏感配置的方案
环境变量注入(最常用)
spring:
datasource:
password: ${DB_PASSWORD}export DB_PASSWORD="MyS3cur3P@ss"Spring Cloud Vault
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>spring:
cloud:
vault:
host: vault.example.com
port: 8200
scheme: https
authentication: TOKEN
token: ${VAULT_TOKEN}
kv:
enabled: true
backend: secret
default-context: my-appJasypt 加密
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>jasypt:
encryptor:
password: ${JASYPT_MASTER_KEY}
algorithm: PBEWithMD5AndDES
spring:
datasource:
password: ENC(encryptedPasswordHere)Kubernetes Secrets
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
DB_PASSWORD: "MyS3cur3P@ss"
---
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_PASSWORD安全方案对比
| 方案 | 适用场景 | 安全等级 | 运维复杂度 |
|---|---|---|---|
| 环境变量 | 快速部署、中小项目 | 中 | 低 |
| Spring Cloud Vault | 大型企业、合规要求高 | 高 | 高 |
| Jasypt | 配置文件需版本控制 | 中 | 中 |
| K8s Secrets | 容器化部署 | 中 | 中 |
| 配置中心 | 微服务架构 | 高 | 高 |
配置中心回退策略
典型架构
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 配置中心 │ │ 本地配置文件 │ │ 环境变量 │
│ (Nacos/Apollo) │ │ application.yml │ │ SERVER_PORT │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
└─────────┬────────────┴────────────┬───────────┘
│ │
┌───────▼────────┐ ┌────────▼───────┐
│ 运行时生效配置 │ │ 回退/兜底配置 │
│ (高优先级) │ │ (低优先级) │
└────────────────┘ └────────────────┘Nacos 配置中心
# 使用 spring.config.import(Spring Boot 3.x 推荐)
spring:
config:
import:
- optional:nacos:my-app.properties?group=DEFAULT_GROUP&namespace=prod
- classpath:application-local.yml # 本地兜底optional: 前缀表示配置中心不可用时应用正常启动;去掉则该依赖不可用时启动失败。
Apollo 配置中心
app.id: my-app
apollo:
bootstrap:
enabled: true
namespaces: application
cacheDir: ./config-cache
connectTimeout: 2000
readTimeout: 5000回退实现原理
public class NacosPropertySourceLocator implements PropertySourceLocator {
@Override
public PropertySource<?> locate(Environment environment) {
try {
return loadFromConfigCenter(); // 1. 优先从配置中心拉取
} catch (Exception ex) {
logger.warn("Config center unavailable, using fallback");
if (isOptional()) return null; // 2. optional:用本地配置
PropertySource<?> cached = loadFromLocalCache();
if (cached != null) return cached; // 3. 使用本地缓存
if (isFailFast()) { // 4. fail-fast:直接失败
throw new IllegalStateException(
"Cannot load config and fail-fast is enabled", ex);
}
return null;
}
}
}完整优先级架构
1. 命令行参数 --server.port=8080 ← 最高
2. 配置中心 可选配置,连接失败可回退到本地
3. JVM 系统属性 -Dserver.port=8080
4. OS 环境变量 SERVER_PORT=8080
5. application-{profile}.yml 针对特定环境的覆盖配置
6. application.yml 项目通用配置
7. @PropertySource 自定义配置文件引入
8. 默认值(代码硬编码)@Value("${server.port:8080}") ← 最低调试配置加载
查看生效配置
方式一:开启 debug: true,启动时输出自动配置报告。
方式二:使用 Actuator 端点:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>management:
endpoints:
web:
exposure:
include: env,configpropsGET /actuator/env → 查看所有环境属性及来源
GET /actuator/configprops → 查看 @ConfigurationProperties 绑定结果方式三:程序化查看 PropertySource 链:
@Component
public class ConfigSourceDebugRunner implements CommandLineRunner {
@Autowired
private ConfigurableEnvironment environment;
@Override
public void run(String... args) {
for (PropertySource<?> ps : environment.getPropertySources()) {
System.out.println(" [" + ps.getName() + "] " + ps.getClass().getSimpleName());
if (ps instanceof EnumerablePropertySource) {
String[] names = ((EnumerablePropertySource<?>) ps).getPropertyNames();
for (String name : names) {
System.out.println(" " + name + " = " + ps.getProperty(name));
}
}
}
}
}常见问题排查
问题:配置未生效
排查步骤:
1. 检查属性名拼写(宽松绑定是否匹配)
2. 检查是否有更高优先级的配置覆盖
3. 检查 Profile 是否正确激活(启动日志:The following 1 profile is active: prod)
4. 检查配置源是否在 PropertySource 链中(/actuator/env 查看)
5. 检查 @ConfigurationProperties 前缀是否正确(/actuator/configprops 查看)总结
Spring Boot 的外部化配置机制通过优先级分层的 PropertySource 链,实现了灵活、可覆盖的配置管理方案。核心要点:
- 17 个配置源严格排序:命令行参数优先,代码默认值兜底
- PropertySource 链:
MutablePropertySources以CopyOnWriteArrayList存储,高优先级在前 - 命令行参数:
SimpleCommandLinePropertySource解析--key=value格式,支持重复 key - 环境变量:
SystemEnvironmentPropertySource提供宽松键匹配(.→_→ 全大写) - YAML 多文档:通过
---分隔,YamlPropertySourceLoader按 profile 筛选 - 类型安全绑定:
Binder配合@ConfigurationProperties自动化映射,支持嵌套和类型转换 - 宽松绑定:驼峰、短横线、下划线、全大写四种风格等价
- 生产实践:环境变量注入加密配置,配置中心提供
optional:回退策略