多环境配置文件
一句话定位:多环境配置文件是 Spring Boot 的按环境拆分配置机制,通过
application-{profile}.yml的命名约定将公共配置与环境特有配置分离,配合多文档 YAML 语法在单文件内实现多环境切换。
定义与作用
多环境配置文件 是 Spring Boot 对 Profile 机制的配置文件层实现。它允许开发者将公共配置放在 application.yml 中,将环境特有配置放在 application-dev.yml、application-test.yml、application-prod.yml 中。当激活某个 Profile 时,Spring Boot 自动加载对应的配置文件,并将其中的属性覆盖到公共配置之上。
命名约定
| 文件名 | 说明 | 激活条件 |
|---|---|---|
application.yml | 公共配置,所有环境共享 | 始终加载 |
application-dev.yml | 开发环境特有配置 | spring.profiles.active=dev |
application-test.yml | 测试环境特有配置 | spring.profiles.active=test |
application-prod.yml | 生产环境特有配置 | spring.profiles.active=prod |
application-{profile}.yml | 任意自定义 Profile | 对应 Profile 激活时加载 |
对比传统 SSM 的繁琐过程
| 维度 | 传统 SSM | Spring Boot 多环境配置文件 |
|---|---|---|
| 文件组织 | 所有环境配置混杂在 applicationContext.xml 中 | 每个环境独立文件,清晰分离 |
| 环境切换 | 手动注释/取消注释 XML 中的配置块 | 一条命令自动加载对应文件 |
| 版本控制 | 生产密码容易误提交到仓库 | 敏感配置放在外部文件,不提交 |
| 可读性 | 一个文件几百行,维护困难 | 每个文件职责单一,一目了然 |
适用位置与常用属性
配置文件加载位置(按优先级)
| 位置 | 优先级 | 说明 |
|---|---|---|
./config/application-{profile}.yml | 高 | JAR 外部 config 子目录,运维最常用 |
./application-{profile}.yml | 中高 | JAR 同级目录 |
classpath:config/application-{profile}.yml | 中 | 打包在 JAR 内的 config 目录 |
classpath:application-{profile}.yml | 低 | 打包在 JAR 内的根目录(本教程默认位置) |
多文档 YAML 语法(Spring Boot 2.4+)
除了拆分成多个文件,Spring Boot 还支持在单个 application.yml 文件内用 --- 分隔多个文档,每个文档独立配置:
# application.yml 多文档结构
spring:
config:
activate:
on-profile: default
server:
port: 8080
---
spring:
config:
activate:
on-profile: dev
server:
port: 8080
debug: true
---
spring:
config:
activate:
on-profile: prod
server:
port: 80
debug: false
| 属性 | 说明 | 示例 |
|---|---|---|
spring.config.activate.on-profile | 指定该文档的生效 Profile | dev |
--- | YAML 多文档分隔符 | 分隔不同环境配置块 |
spring.profiles.active | 激活的 Profile 列表 | dev,localcache |
spring.profiles.include | 无条件额外包含的 Profile | common |
核心原理
多环境配置文件加载与合并流程
图解释:Spring Boot 启动时先加载 application.yml(公共配置),然后根据激活的 Profile 加载对应的 application-{profile}.yml。两文件的配置合并到 Environment 中,Profile 特有配置的同名属性覆盖公共配置,不同名属性共存。
文件层级与优先级关系
图解释:文件优先级遵循"外部 > 内部、Profile 文件 > 非 Profile 文件、config 目录 > 当前目录"的规则。运维在生产环境部署时,通常将 application-prod.yml 放在 ./config/ 目录下,确保其优先级最高且不会被误打包进 JAR。
完整示例
场景说明
飞翔科技的学生成绩管理系统需要部署到三种环境:
- dev:本地开发,H2 数据库,端口 8080,开启 SQL 日志
- test:QA 测试,MySQL 测试库,端口 8081,关闭 SQL 日志
- prod:线上生产,MySQL 生产库,端口 80,关闭 SQL 日志,开启 SSL
后端开发小崔需要为每种环境准备独立的配置文件,同时确保公共配置(如应用名称、版本)不需要重复写三份。
操作前:所有配置塞在一个文件,混乱不堪
# 操作前:一个 application.yml 塞了所有环境的配置,靠注释切换
server:
port: 8080 # dev:8080, test:8081, prod:80
spring:
datasource:
# 开发用 H2
url: jdbc:h2:mem:student_db
driver-class-name: org.h2.Driver
# 测试用 MySQL(需要手动取消注释)
# url: jdbc:mysql://test-db:3306/student_db
# driver-class-name: com.mysql.cj.jdbc.Driver
# username: test_user
# password: test_pass
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
# debug-mode: true # dev 开启
debug-mode: false # 其他环境关闭(需要手动改)
痛点:
- 小崔在 dev 时把
debug-mode改成true,提交到仓库后,测试同事启动时也是true - 生产数据库密码写在公共配置文件中,提交到 Git 仓库有安全隐患
- 每次切换环境都要手动改 YAML 并重新编译,极易出错
使用多环境配置文件的完整代码
步骤1:公共配置 application.yml
# src/main/resources/application.yml(公共配置,所有环境共享)
server:
port: 8080
spring:
application:
name: student-system
profiles:
active: dev # 默认激活 dev,方便本地开发
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
version: "2.7.18"
max-upload-size: 10MB
步骤2:开发环境 application-dev.yml
# src/main/resources/application-dev.yml(dev 环境特有)
spring:
datasource:
url: jdbc:h2:mem:student_db;DB_CLOSE_DELAY=-1;MODE=MySQL
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
show-sql: true # 开发环境打印 SQL,方便调试
hibernate:
ddl-auto: create-drop # 每次启动重建表
feixiang:
student:
debug-mode: true
log-level: DEBUG
allowed-origins: "*"
步骤3:测试环境 application-test.yml
# src/main/resources/application-test.yml(test 环境特有)
spring:
datasource:
url: jdbc:mysql://test-db:3306/student_db?useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: test_user
password: test_pass
jpa:
show-sql: false
hibernate:
ddl-auto: validate # 只验证表结构,不修改
feixiang:
student:
debug-mode: false
log-level: WARN
allowed-origins: "http://test.feixiang.com"
步骤4:生产环境 application-prod.yml
# src/main/resources/application-prod.yml(prod 环境特有,打包时不包含敏感信息)
server:
port: 80
spring:
datasource:
url: jdbc:mysql://prod-db:3306/student_db?useSSL=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USERNAME} # 从环境变量读取,不硬编码
password: ${DB_PASSWORD} # 从环境变量读取,不硬编码
jpa:
show-sql: false
hibernate:
ddl-auto: none # 生产环境禁止自动修改表结构
feixiang:
student:
debug-mode: false
log-level: ERROR
allowed-origins: "https://student.feixiang.com"
生产安全提示:
application-prod.yml中的数据库密码使用${DB_USERNAME}占位符,实际值通过服务器环境变量传入。该文件可以放入代码仓库(不含真实密码),真实密码由运维在部署时配置。
步骤5:不同环境启动验证
package com.feixiang.student.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class EnvironmentService {
@Value("${server.port}")
private int port;
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${feixiang.student.debug-mode}")
private boolean debugMode;
@Value("${feixiang.student.log-level}")
private String logLevel;
public void printEnvironment() {
System.out.println("=== 当前环境配置 ===");
System.out.println("端口: " + port);
System.out.println("数据库: " + dbUrl);
System.out.println("调试模式: " + debugMode);
System.out.println("日志级别: " + logLevel);
}
}
操作后运行结果及分析
dev 环境启动(默认,spring.profiles.active: dev):
=== 当前环境配置 ===
端口: 8080
数据库: jdbc:h2:mem:student_db;DB_CLOSE_DELAY=-1;MODE=MySQL
调试模式: true
日志级别: DEBUG
test 环境启动(--spring.profiles.active=test):
=== 当前环境配置 ===
端口: 8080 ← 来自 application.yml 公共配置
数据库: jdbc:mysql://test-db:3306/student_db?useSSL=false&serverTimezone=Asia/Shanghai
调试模式: false
日志级别: WARN
prod 环境启动(--spring.profiles.active=prod):
=== 当前环境配置 ===
端口: 80 ← 被 application-prod.yml 覆盖
数据库: jdbc:mysql://prod-db:3306/student_db?useSSL=true&serverTimezone=Asia/Shanghai
调试模式: false
日志级别: ERROR
分析:
- 公共配置复用:
system-name、version等公共属性只在application.yml中写一次,所有环境共享 - 环境特有覆盖:
application-dev.yml中的debug-mode: true只在 dev 环境生效,test/prod 使用各自配置 - 端口差异化:
application-prod.yml将端口覆盖为 80,dev/test 使用公共默认值 8080 - 数据库隔离:三种环境使用完全不同的数据库连接,彻底避免"开发连到生产库"的事故
- 安全密码管理:生产数据库密码通过环境变量
${DB_PASSWORD}传入,不写入代码仓库
易错场景与面试考点
易错场景一:多文档 YAML 中 spring.profiles 语法过时
小崔在 Spring Boot 2.7 中使用旧版语法写多文档 YAML:
# 错误示范:Spring Boot 2.4+ 已废弃的语法
spring:
profiles: dev # ← 旧语法,不生效!
server:
port: 8080
---
spring:
profiles: prod # ← 旧语法,不生效!
server:
port: 80
后果:Spring Boot 2.4 开始将 spring.profiles 改为 spring.config.activate.on-profile。使用旧语法时,Profile 条件判断失效,所有文档的配置都被加载并合并,导致端口等属性被错误覆盖。
正确做法:Spring Boot 2.4+ 必须使用新语法:
spring:
config:
activate:
on-profile: dev
server:
port: 8080
---
spring:
config:
activate:
on-profile: prod
server:
port: 80
易错场景二:Profile 文件放错位置导致未加载
小崔把 application-prod.yml 放在 src/main/resources/config/ 目录下:
src/main/resources/
├── application.yml
└── config/
└── application-prod.yml # ← 小崔放这里
然后在 JAR 外部又放了一个 application-prod.yml:
/opt/apps/
├── student-system.jar
└── application-prod.yml # ← 运维放这里
后果:Spring Boot 的加载顺序是 ./config/ > ./ > classpath:config/ > classpath/。如果运维在 ./config/ 也放了一个文件,classpath:config/application-prod.yml 会被完全覆盖。小崔以为自己的文件生效了,实际运行的是运维的文件。
正确做法:在团队协作中约定统一的文件放置规范。开发阶段统一放在 src/main/resources/(classpath),运维覆盖阶段统一放在 ./config/(JAR 外部)。避免多位置文件互相覆盖导致困惑。
面试考点
Q:application.yml 和 application-dev.yml 的加载顺序是什么?同名属性谁覆盖谁?
先加载
application.yml(公共配置),再加载application-dev.yml(Profile 特有配置)。application-dev.yml中的同名属性覆盖application.yml中的值。不同名属性共存,合并为最终配置。
Q:多文档 YAML 中 --- 的作用是什么?
---是 YAML 标准的多文档分隔符。在 Spring Boot 的application.yml中,它用于将单个文件拆分为多个独立的配置文档,每个文档可以有自己的spring.config.activate.on-profile条件。适合配置项较少的场景,避免创建过多文件。
Q:生产环境的数据库密码如何安全管理?
三种方式:1.
application-prod.yml中使用${DB_PASSWORD}占位符,实际值通过环境变量传入;2. 将application-prod.yml放在 JAR 外部的./config/目录,不打包进 JAR;3. 使用配置中心(Spring Cloud Config)集中管理敏感配置。本教程推荐前两种结合使用。
Q:spring.profiles.active 和 spring.profiles.include 有什么区别?
spring.profiles.active是显式激活的 Profile 列表,决定加载哪些application-{profile}.yml;spring.profiles.include是无条件包含的额外 Profile,无论active是什么都会加载。例如active: prod+include: metrics会同时加载application-prod.yml和application-metrics.yml。
Q:Spring Boot 的 Profile 配置文件可以放在哪些位置?
四个位置,按优先级从高到低:1.
./config/(JAR 外部 config 子目录);2../(JAR 同级目录);3.classpath:config/(JAR 内部的 config 目录);4.classpath:/(JAR 内部的根目录)。运维常用./config/放置生产配置,因为优先级最高且不随 JAR 包重新打包。