外部化配置
一句话定位:外部化配置是 Spring Boot 的配置与代码分离机制,允许通过
application.yml、环境变量、命令行参数等外部来源覆盖应用行为,实现同一份代码在不同环境运行不同配置。
定义与作用
外部化配置(Externalized Configuration) 是 Spring Boot 的核心理念之一,它将应用的配置参数(如数据库连接、端口号、功能开关)从 Java 代码中抽离,放到外部文件或环境中。这样做的好处是:打包后的 JAR 包不需要修改即可在 dev、test、prod 等不同环境运行。
对比传统 SSM 的繁琐过程
| 维度 | 传统 SSM(Spring + Spring MVC + MyBatis) | Spring Boot 外部化配置 |
|---|---|---|
| 配置位置 | 分散在 applicationContext.xml、<property> 标签中 | 统一在 application.yml 或外部来源 |
| 环境切换 | 需要修改 XML 文件重新打包 | 只需修改外部配置或启动参数 |
| 部署方式 | 修改配置 → 重新编译 → 打 WAR → 部署到 Tomcat | java -jar 启动,配置通过外部传入 |
| 优先级 | 无统一优先级,靠 XML 加载顺序 | 17级明确优先级,高覆盖低 |
| 运维友好度 | 差,配置藏在代码包内部 | 优,配置与代码完全分离 |
适用位置与常用属性
配置来源一览
Spring Boot 支持从以下位置读取配置(按优先级从高到低排列):
| 来源 | 说明 | 示例 |
|---|---|---|
| 命令行参数 | 启动时传入的 --key=value | --server.port=9090 |
SPRING_APPLICATION_JSON | 环境变量中的 JSON 字符串 | {"server":{"port":9090}} |
| JNDI 属性 | Servlet 容器中的 JNDI 绑定 | java:comp/env/spring.datasource.url |
| ServletConfig 初始化参数 | web.xml 中的 <init-param> | 传统 WAR 部署场景 |
| ServletContext 初始化参数 | web.xml 中的 <context-param> | 传统 WAR 部署场景 |
| 系统属性(System Properties) | JVM 启动参数 -D | -Dserver.port=9090 |
| 环境变量(Environment Variables) | 操作系统环境变量 | SERVER_PORT=9090 |
application.yml / application.properties | 打包在 JAR 内或外部的配置文件 | application.yml |
@PropertySource | 自定义配置文件路径 | @PropertySource("classpath:custom.yml") |
SpringApplication.setDefaultProperties() | 代码中硬编码的默认值 | setDefaultProperties(...) |
spring-boot-devtools 全局设置 | 开发者工具全局配置文件 | ~/.spring-boot-devtools.properties |
统一使用 application.yml
本教程统一采用 YAML 格式,所有示例围绕飞翔科技学生成绩管理系统:
# application.yml(dev 环境示例)
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:student_db;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
version: "2.7.18"
features:
- cache
- log
核心原理
17级配置源优先级全景
图解释:Spring Boot 启动时,按从上到下的顺序将所有配置源加载到 Environment 中。高优先级的属性值会覆盖低优先级的同名属性。例如命令行参数 --server.port=9090 会覆盖 application.yml 中的 server.port: 8080。
配置读取与注入流程
核心组件:
ConfigurableEnvironment:持有所有PropertySource,按优先级排序ConfigurationPropertiesBinder:负责将外部属性绑定到 Java 对象ConversionService:将字符串值转换为目标类型(如"5000"→int)
完整示例
场景说明
飞翔科技学生成绩管理系统需要支持三种环境:
- dev:本地开发,使用 H2 内存数据库,端口 8080
- test:测试环境,使用 MySQL 测试库,端口 8081
- prod:生产环境,使用 MySQL 生产库,端口 80
架构师白歌要求小崔确保同一份 JAR 包可以通过外部配置在不同环境运行,不能为了换环境重新打包。
操作前:配置硬编码在代码中
// 操作前:配置硬编码,环境切换必须改代码重新编译
package com.feixiang.student.config;
public class HardcodedConfig {
// 数据库配置写死在代码里
private String dbUrl = "jdbc:h2:mem:student_db";
private String dbDriver = "org.h2.Driver";
private String dbUsername = "sa";
private String dbPassword = "";
// 系统信息也写死
private String systemName = "飞翔科技学生成绩管理系统";
private int serverPort = 8080;
// 如果要切换到 MySQL 生产环境,必须修改源代码、重新编译、重新打包
// 这是严重的设计缺陷
}
痛点:
- 每切换一次环境就要改代码、重新编译、重新打包
- 数据库密码等敏感信息暴露在代码仓库中
- 无法做到"一次构建,到处运行"
使用外部化配置的完整代码
步骤1:创建统一的 application.yml
# src/main/resources/application.yml
server:
port: 8080
spring:
application:
name: student-system
datasource:
url: jdbc:h2:mem:student_db;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
version: "2.7.18"
max-upload-size: 10MB
features:
- cache
- log
- monitor
步骤2:在代码中通过 @Value 读取配置
package com.feixiang.student.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SystemConfig {
@Value("${feixiang.student.system-name}")
private String systemName;
@Value("${feixiang.student.version}")
private String version;
@Value("${server.port:8080}")
private int serverPort;
@Value("${spring.datasource.url}")
private String databaseUrl;
public void printConfig() {
System.out.println("系统名称: " + systemName);
System.out.println("版本: " + version);
System.out.println("端口: " + serverPort);
System.out.println("数据库: " + databaseUrl);
}
}
步骤3:通过外部配置覆盖(不修改代码)
切换到测试环境时,只需在启动时传入外部配置:
# 方式1:命令行参数覆盖(最高优先级)
java -jar student-system.jar \
--server.port=8081 \
--spring.datasource.url=jdbc:mysql://test-db:3306/student_db \
--spring.datasource.username=test_user \
--spring.datasource.password=test_pass
# 方式2:环境变量覆盖(Linux/Mac)
export SERVER_PORT=8081
export SPRING_DATASOURCE_URL=jdbc:mysql://test-db:3306/student_db
java -jar student-system.jar
# 方式3:外部配置文件(./config/application.yml 优先级高于 JAR 内部)
# 在 JAR 同级目录创建 config/application.yml,写入测试环境配置
操作后运行结果及分析
启动时控制台输出:
系统名称: 飞翔科技学生成绩管理系统
版本: 2.7.18
端口: 8081
数据库: jdbc:mysql://test-db:3306/student_db
分析:
- 配置与代码分离:
SystemConfig类中只声明了配置项的名称,具体值来自外部 - 环境切换零代码改动:从 dev 切换到 test 时,
application.yml中的默认值被命令行参数覆盖 - 优先级生效:
--server.port=8081(命令行,优先级1)覆盖了application.yml中的server.port: 8080(优先级14) - 敏感信息不外泄:生产数据库密码通过环境变量传入,不写入代码仓库
易错场景与面试考点
易错场景一:YAML 缩进错误导致配置不生效
小崔在写 application.yml 时,不小心把 feixiang.student 写成了同层级:
# 错误示范:缩进错误
feixiang:
student: # ← 缩进错误!应该再缩进两格
system-name: "飞翔科技学生成绩管理系统"
后果:YAML 解析器认为 student 是 feixiang 的同级节点,而不是子节点。@Value("${feixiang.student.system-name}") 读取不到值,抛出 IllegalArgumentException: Could not resolve placeholder 'feixiang.student.system-name'。
正确做法:YAML 对缩进极其敏感,必须使用2个空格(不能是 Tab),严格保持层级关系:
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
易错场景二:环境变量命名不规范导致绑定失败
小崔在 Linux 服务器上设置了环境变量:
# 错误示范:环境变量命名不符合松散绑定规则
export feixiang.student.max-upload-size=20MB
后果:环境变量名中包含点号(.)在大多数 Shell 中不合法,且 Spring Boot 的环境变量松散绑定规则要求将点号替换为下划线并转为大写。即使 Shell 允许,Spring Boot 的 SystemEnvironmentPropertySource 也无法正确解析。
正确做法:环境变量使用全大写,点号替换为下划线:
export FEIXIANG_STUDENT_MAX_UPLOAD_SIZE=20MB
export SPRING_DATASOURCE_URL=jdbc:mysql://prod-db:3306/student_db
面试考点
Q:Spring Boot 外部化配置的核心设计思想是什么?
约定优于配置 + 配置与代码分离。Spring Boot 通过
application.yml提供合理的默认值,同时允许通过外部来源(命令行、环境变量等)覆盖,实现同一份代码在不同环境运行。
Q:Spring Boot 的配置文件有哪些加载位置?优先级如何?
从低到高:classpath 根目录 → classpath 的
config/子目录 → JAR 所在目录 → JAR 同级config/子目录。外部目录的配置文件优先级高于 JAR 内部的文件。完整优先级共17级,命令行参数最高,代码中的默认值最低。
Q:YAML 和 Properties 格式如何选择?
YAML 适合层级化的复杂配置(如嵌套对象、List),可读性更好;Properties 适合扁平化配置。Spring Boot 官方推荐 YAML,本教程统一使用
application.yml。
Q:环境变量 SERVER_PORT 为什么能映射到 server.port?
Spring Boot 的
RelaxedDataBinder支持松散绑定。环境变量SERVER_PORT经过转换(去掉下划线、转小写)后可以匹配到server.port。同理,FEIXIANG_STUDENT_MAX_UPLOAD_SIZE可以匹配到feixiang.student.max-upload-size。