在一个中型电商项目中,开发团队遇到了这样的困境:张三在本地开发时使用本地的MySQL,李四在测试环境部署时连接测试数据库,王五在生产环境发布时又需要不同的Redis配置。更糟糕的是,每个环境的敏感信息(密码、密钥)都不相同,如何安全地管理这些配置?
开发人员们最初的做法是:为每个环境创建不同的配置文件,手动修改后提交。结果可想而知:
- 开发人员A不小心将生产数据库密码提交到了Git
- 开发人员B忘记修改Redis地址,导致测试环境连不上缓存
- 开发人员C的本地配置污染了代码仓库
这些问题,都源于对Spring Boot配置文件application.yml的理解不够深入。
一、 application.yml 基础概念
1.1 YAML vs Properties
Spring Boot支持两种配置文件格式:.properties和.yml。YAML(YAML Ain't Markup Language)因其结构清晰、可读性强,已成为企业级项目的首选。
Properties格式:
properties
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
YAML格式:
yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
YAML的优势:
- 层次结构清晰,缩进表示层级
- 支持复杂数据结构(列表、Map)
- 注释使用
#,与Shell脚本一致 - 支持多文档块(通过
---分隔)
1.2 Spring Boot 3.2.x 配置加载顺序
理解配置加载顺序是排查配置问题的关键:
1. 默认属性(通过SpringApplication.setDefaultProperties定义)
2. @Configuration类上的@PropertySource注解
3. 配置文件(application.properties或application.yml)
4. 随机属性(RandomValuePropertySource)
5. 操作系统环境变量
6. Java系统属性(System.getProperties())
7. JNDI属性(java:comp/env)
8. ServletContext初始化参数
9. ServletConfig初始化参数
10. SPRING_APPLICATION_JSON中的属性(内嵌JSON)
11. 命令行参数
12. 测试属性(@SpringBootTest)
13. 测试@PropertySource注解
14. DevTools全局配置(~/.spring-boot-devtools.properties)
注意 :后加载的配置会覆盖先加载的配置,我们可以通过环境变量、命令行参数等方式覆盖打包在Jar内的配置。
二、 企业级 application.yml 示例
2.1 完整配置文件(application.yml)
yaml
# ============================================
# 应用基础配置
# ============================================
server:
port: 8080
servlet:
context-path: /api
encoding:
charset: UTF-8
force: true
tomcat:
max-threads: 200
min-spare-threads: 10
connection-timeout: 5000ms
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024
spring:
# ============================================
# 应用信息
# ============================================
application:
name: spring-boot-demo
version: 1.0.0
# ============================================
# 多环境配置
# ============================================
profiles:
active: @activatedProperties@ # Maven Profile动态替换
# ============================================
# 数据源配置(MySQL 8.0.x + HikariCP)
# ============================================
datasource:
# 使用环境变量,避免敏感信息硬编码
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: ${DB_USER:root}
password: ${DB_PASS:123456}
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
pool-name: HikariCP
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
validation-timeout: 5000
# ============================================
# Redis配置(Redis 7.x + Lettuce)
# ============================================
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASS:}
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 1000ms
shutdown-timeout: 100ms
# ============================================
# JPA/Hibernate配置(可选)
# ============================================
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
jdbc:
batch_size: 20
# ============================================
# 事务管理
# ============================================
transaction:
rollback-on-commit-failure: true
# ============================================
# 消息队列(RabbitMQ示例)
# ============================================
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: 5672
username: ${RABBITMQ_USER:guest}
password: ${RABBITMQ_PASS:guest}
virtual-host: /
listener:
simple:
prefetch: 10
concurrency: 5
max-concurrency: 20
# ============================================
# 邮件配置
# ============================================
mail:
host: smtp.example.com
port: 587
username: ${MAIL_USER}
password: ${MAIL_PASS}
properties:
mail:
smtp:
auth: true
starttls:
enable: true
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000
# ============================================
# 缓存配置
# ============================================
cache:
type: redis
redis:
time-to-live: 600000
cache-null-values: false
key-prefix: "cache:"
use-key-prefix: true
# ============================================
# 文件上传
# ============================================
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
# ============================================
# 任务执行与调度
# ============================================
task:
execution:
pool:
core-size: 8
max-size: 20
queue-capacity: 50
keep-alive: 60s
thread-name-prefix: async-task-
scheduling:
pool:
size: 5
thread-name-prefix: scheduled-task-
# ============================================
# MyBatis-Plus配置
# ============================================
mybatis-plus:
# 扫描mapper接口
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.example.demo.entity
global-config:
db-config:
# 逻辑删除
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# ID生成策略
id-type: ASSIGN_ID
banner: false
configuration:
# 下划线转驼峰
map-underscore-to-camel-case: true
# 日志实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启二级缓存
cache-enabled: true
# 返回结果映射
auto-mapping-behavior: full
# 返回null的字段不显示
call-setters-on-nulls: true
# ============================================
# SpringDoc OpenAPI配置
# ============================================
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
operations-sorter: method
tags-sorter: alpha
show-actuator: true
default-consumes-media-type: application/json
default-produces-media-type: application/json
writer-with-default-pretty-printer: true
packages-to-scan: com.example.demo.controller
paths-to-match: /api/**
# ============================================
# 应用监控(Actuator)
# ============================================
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /actuator
endpoint:
health:
show-details: always
probes:
enabled: true
prometheus:
enabled: true
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
info:
env:
enabled: true
java:
enabled: true
os:
enabled: true
# ============================================
# 日志配置
# ============================================
logging:
# 日志级别
level:
root: INFO
com.example.demo: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
# 日志文件
file:
name: logs/${spring.application.name}.log
# 日志格式
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
# Logback配置
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
total-size-cap: 1GB
clean-history-on-start: false
# ============================================
# 自定义配置
# ============================================
app:
# 应用配置
config:
# 版本信息
version: 1.0.0
# 文件上传路径
upload-path: /data/uploads
# 临时文件路径
temp-path: /tmp
# 安全配置
security:
# JWT配置
jwt:
secret: ${JWT_SECRET:your-jwt-secret-key-change-in-production}
expiration: 86400
header: Authorization
token-prefix: "Bearer "
# 加密密钥
encryption:
aes-key: ${AES_KEY:default-aes-key-change-me}
# 业务配置
business:
# 分页配置
page:
max-size: 100
default-size: 20
# 缓存配置
cache:
user-info-ttl: 300
order-info-ttl: 600
# 重试配置
retry:
max-attempts: 3
backoff-delay: 1000
2.2 多环境配置文件
开发环境配置(application-dev.yml):
yaml
# 开发环境配置
spring:
config:
activate:
on-profile: dev
# 开发环境使用H2内存数据库
h2:
console:
enabled: true
path: /h2-console
datasource:
url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
driver-class-name: org.h2.Driver
username: sa
password:
# 开发环境显示详细错误
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
# 开发环境开启SQL日志
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging:
level:
root: INFO
com.example.demo: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type: TRACE
测试环境配置(application-test.yml):
yaml
# 测试环境配置
spring:
config:
activate:
on-profile: test
datasource:
url: jdbc:mysql://test-db:3306/demo_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: ${TEST_DB_USER}
password: ${TEST_DB_PASS}
data:
redis:
host: test-redis
password: ${TEST_REDIS_PASS}
logging:
level:
root: INFO
com.example.demo: INFO
org.springframework: WARN
生产环境配置(application-prod.yml):
yaml
# 生产环境配置
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-db-master:3306/demo_prod?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=true&requireSSL=true
username: ${PROD_DB_USER}
password: ${PROD_DB_PASS}
hikari:
maximum-pool-size: 50
minimum-idle: 10
data:
redis:
host: ${PROD_REDIS_CLUSTER}
password: ${PROD_REDIS_PASS}
lettuce:
pool:
max-active: 50
max-idle: 20
# 生产环境关闭开发工具
devtools:
add-properties: false
# 生产环境使用更安全的配置
mvc:
pathmatch:
matching-strategy: ant_path_matcher
jackson:
default-property-inclusion: non_null
# 生产环境关闭Swagger UI
springdoc:
swagger-ui:
enabled: false
api-docs:
enabled: false
# 生产环境只暴露必要的监控端点
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: when_authorized
shutdown:
enabled: false
# 生产环境更严格的日志级别
logging:
level:
root: WARN
com.example.demo: INFO
file:
name: /var/log/${spring.application.name}/app.log
logback:
rollingpolicy:
max-file-size: 50MB
max-history: 90
total-size-cap: 10GB
2.3 配置属性绑定类
AppConfigProperties.java - 自定义配置绑定类:
java
package com.example.demo.config;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import java.time.Duration;
import java.util.List;
import java.util.Map;
/**
* 自定义应用配置属性
* 通过@ConfigurationProperties绑定application.yml中的app前缀配置
*
* @author 专栏作者
* @version 1.0
* @since 2026-05-04
*/
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "app")
public class AppConfigProperties {
/**
* 应用配置
*/
private Config config = new Config();
/**
* 安全配置
*/
private Security security = new Security();
/**
* 业务配置
*/
private Business business = new Business();
@Data
public static class Config {
/**
* 应用版本
*/
@NotBlank(message = "应用版本不能为空")
private String version = "1.0.0";
/**
* 文件上传路径
*/
@NotBlank(message = "文件上传路径不能为空")
private String uploadPath = "/data/uploads";
/**
* 临时文件路径
*/
@NotBlank(message = "临时文件路径不能为空")
private String tempPath = "/tmp";
}
@Data
public static class Security {
/**
* JWT配置
*/
private Jwt jwt = new Jwt();
/**
* 加密配置
*/
private Encryption encryption = new Encryption();
@Data
public static class Jwt {
/**
* JWT密钥
*/
@NotBlank(message = "JWT密钥不能为空")
private String secret = "your-jwt-secret-key-change-in-production";
/**
* 过期时间(秒)
*/
@Min(value = 60, message = "JWT过期时间不能少于60秒")
@Max(value = 2592000, message = "JWT过期时间不能超过30天")
private Long expiration = 86400L;
/**
* Header名称
*/
@NotBlank(message = "JWT Header名称不能为空")
private String header = "Authorization";
/**
* Token前缀
*/
@NotBlank(message = "JWT Token前缀不能为空")
private String tokenPrefix = "Bearer ";
}
@Data
public static class Encryption {
/**
* AES加密密钥
*/
@NotBlank(message = "AES加密密钥不能为空")
private String aesKey = "default-aes-key-change-me";
}
}
@Data
public static class Business {
/**
* 分页配置
*/
private Page page = new Page();
/**
* 缓存配置
*/
private Cache cache = new Cache();
/**
* 重试配置
*/
private Retry retry = new Retry();
@Data
public static class Page {
/**
* 最大分页大小
*/
@Min(value = 1, message = "最大分页大小不能小于1")
@Max(value = 1000, message = "最大分页大小不能大于1000")
private Integer maxSize = 100;
/**
* 默认分页大小
*/
@Min(value = 1, message = "默认分页大小不能小于1")
@Max(value = 100, message = "默认分页大小不能大于100")
private Integer defaultSize = 20;
}
@Data
public static class Cache {
/**
* 用户信息缓存时间(秒)
*/
@Min(value = 60, message = "用户信息缓存时间不能少于60秒")
private Long userInfoTtl = 300L;
/**
* 订单信息缓存时间(秒)
*/
@Min(value = 60, message = "订单信息缓存时间不能少于60秒")
private Long orderInfoTtl = 600L;
}
@Data
public static class Retry {
/**
* 最大重试次数
*/
@Min(value = 1, message = "最大重试次数不能小于1")
@Max(value = 10, message = "最大重试次数不能大于10")
private Integer maxAttempts = 3;
/**
* 退避延迟(毫秒)
*/
@Min(value = 0, message = "退避延迟不能小于0")
private Long backoffDelay = 1000L;
}
}
/**
* 获取完整的配置信息
* @return 配置信息Map
*/
public Map<String, Object> getAllConfig() {
return Map.of(
"config", config,
"security", Map.of(
"jwt", security.getJwt(),
"encryption", security.getEncryption()
),
"business", Map.of(
"page", business.getPage(),
"cache", business.getCache(),
"retry", business.getRetry()
)
);
}
}
2.4 配置验证测试类
ConfigurationTest.java - 测试配置是否正确加载:
java
package com.example.demo;
import com.example.demo.config.AppConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 配置加载测试
* 验证application.yml配置是否正确加载
*
* @author 专栏作者
* @version 1.0
* @since 2026-05-04
*/
@Slf4j
@SpringBootTest
class ConfigurationTest {
@Autowired
private Environment environment;
@Autowired
private AppConfigProperties appConfigProperties;
@Test
void testBasicConfiguration() {
// 测试基本配置
String appName = environment.getProperty("spring.application.name");
String serverPort = environment.getProperty("server.port");
log.info("应用名称: {}", appName);
log.info("服务器端口: {}", serverPort);
assertThat(appName).isEqualTo("spring-boot-demo");
assertThat(serverPort).isEqualTo("8080");
}
@Test
void testDatabaseConfiguration() {
// 测试数据库配置
String driverClass = environment.getProperty("spring.datasource.driver-class-name");
Integer maxPoolSize = environment.getProperty("spring.datasource.hikari.maximum-pool-size", Integer.class);
log.info("数据库驱动: {}", driverClass);
log.info("连接池最大大小: {}", maxPoolSize);
assertThat(driverClass).isEqualTo("com.mysql.cj.jdbc.Driver");
assertThat(maxPoolSize).isEqualTo(20);
}
@Test
void testCustomConfiguration() {
// 测试自定义配置
log.info("应用版本: {}", appConfigProperties.getConfig().getVersion());
log.info("上传路径: {}", appConfigProperties.getConfig().getUploadPath());
log.info("JWT密钥: {}", appConfigProperties.getSecurity().getJwt().getSecret());
log.info("最大分页大小: {}", appConfigProperties.getBusiness().getPage().getMaxSize());
assertThat(appConfigProperties.getConfig().getVersion()).isEqualTo("1.0.0");
assertThat(appConfigProperties.getConfig().getUploadPath()).isEqualTo("/data/uploads");
assertThat(appConfigProperties.getBusiness().getPage().getMaxSize()).isEqualTo(100);
}
@Test
void testProfileConfiguration() {
// 测试环境配置
String[] activeProfiles = environment.getActiveProfiles();
log.info("激活的环境: {}", (Object) activeProfiles);
// 如果没有显式设置,使用默认环境
if (activeProfiles.length == 0) {
log.info("使用默认配置");
} else {
for (String profile : activeProfiles) {
log.info("环境 {} 的配置已加载", profile);
}
}
}
@Test
void testConfigurationHierarchy() {
// 测试配置层次结构
log.info("=== 配置层次结构测试 ===");
// 1. 系统属性
String javaVersion = System.getProperty("java.version");
log.info("Java版本: {}", javaVersion);
// 2. 环境变量
String userHome = System.getenv("HOME");
log.info("用户目录: {}", userHome);
// 3. Spring配置
String appNameFromEnv = environment.getProperty("spring.application.name");
log.info("从Environment获取应用名称: {}", appNameFromEnv);
// 验证配置来源
assertThat(appNameFromEnv).isEqualTo("spring-boot-demo");
// 打印所有配置
log.info("=== 当前所有配置 ===");
log.info("数据库URL: {}", environment.getProperty("spring.datasource.url"));
log.info("Redis主机: {}", environment.getProperty("spring.data.redis.host"));
log.info("MyBatis日志实现: {}", environment.getProperty("mybatis-plus.configuration.log-impl"));
log.info("Swagger UI路径: {}", environment.getProperty("springdoc.swagger-ui.path"));
}
}
运行测试结果:
2026-05-04 10:30:15.123 INFO com.example.demo.ConfigurationTest - 应用名称: spring-boot-demo
2026-05-04 10:30:15.124 INFO com.example.demo.ConfigurationTest - 服务器端口: 8080
2026-05-04 10:30:15.125 INFO com.example.demo.ConfigurationTest - 数据库驱动: com.mysql.cj.jdbc.Driver
2026-05-04 10:30:15.125 INFO com.example.demo.ConfigurationTest - 连接池最大大小: 20
2026-05-04 10:30:15.126 INFO com.example.demo.ConfigurationTest - 应用版本: 1.0.0
2026-05-04 10:30:15.126 INFO com.example.demo.ConfigurationTest - 上传路径: /data/uploads
2026-05-04 10:30:15.126 INFO com.example.demo.ConfigurationTest - JWT密钥: your-jwt-secret-key-change-in-production
2026-05-04 10:30:15.127 INFO com.example.demo.ConfigurationTest - 最大分页大小: 100
2026-05-04 10:30:15.127 INFO com.example.demo.ConfigurationTest - 激活的环境: []
2026-05-04 10:30:15.127 INFO com.example.demo.ConfigurationTest - 使用默认配置
2026-05-04 10:30:15.127 INFO com.example.demo.ConfigurationTest - === 配置层次结构测试 ===
2026-05-04 10:30:15.128 INFO com.example.demo.ConfigurationTest - Java版本: 17.0.5
2026-05-04 10:30:15.128 INFO com.example.demo.ConfigurationTest - 用户目录: /Users/username
2026-05-04 10:30:15.128 INFO com.example.demo.ConfigurationTest - 从Environment获取应用名称: spring-boot-demo
2026-05-04 10:30:15.129 INFO com.example.demo.ConfigurationTest - === 当前所有配置 ===
2026-05-04 10:30:15.129 INFO com.example.demo.ConfigurationTest - 数据库URL: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
2026-05-04 10:30:15.129 INFO com.example.demo.ConfigurationTest - Redis主机: localhost
2026-05-04 10:30:15.130 INFO com.example.demo.ConfigurationTest - MyBatis日志实现: org.apache.ibatis.logging.stdout.StdOutImpl
2026-05-04 10:30:15.130 INFO com.example.demo.ConfigurationTest - Swagger UI路径: /swagger-ui.html
三、 企业级最佳实践
3.1 配置管理原则
- 配置外部化:永远不要在代码中硬编码配置
- 环境隔离:使用Profile严格区分不同环境
- 敏感信息保护:密码、密钥等通过环境变量注入
- 配置验证 :使用
@Validated注解验证配置 - 配置监控:通过Actuator端点监控配置状态
3.2 安全配置实践
yaml
# 安全配置示例
security:
# 永远不要硬编码密码
# ❌ 错误做法
# password: mysecretpassword
# ✅ 正确做法 - 使用环境变量
password: ${DB_PASSWORD}
# ✅ 或者使用配置中心
# spring.cloud.config.uri: http://config-server:8888
# 生产环境强制使用SSL
datasource:
url: jdbc:mysql://prod-db:3306/db?useSSL=true&requireSSL=true&verifyServerCertificate=true
# 加密敏感配置
jasypt:
encryptor:
password: ${JASYPT_PASSWORD}
algorithm: PBEWithMD5AndDES
3.3 性能优化配置
yaml
# 性能优化配置
spring:
datasource:
hikari:
# 连接池配置
maximum-pool-size: ${DB_POOL_SIZE:20}
minimum-idle: ${DB_MIN_IDLE:5}
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
data:
redis:
lettuce:
pool:
# Redis连接池
max-active: 20
max-idle: 10
min-idle: 5
max-wait: 1000ms
# 线程池配置
task:
execution:
pool:
core-size: 8
max-size: 20
queue-capacity: 50
server:
tomcat:
# Tomcat优化
max-threads: 200
min-spare-threads: 10
accept-count: 100
connection-timeout: 5000ms
四、 常见错误与解决方案
错误1:配置属性名错误
yaml
# ❌ 错误:属性名错误
spring:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver # 应该是driver-class-name
url: jdbc:mysql://localhost:3306/db
# ✅ 正确
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db
解决方案:使用IDE的配置提示功能,或查阅官方文档确认属性名。
错误2:配置值类型错误
yaml
# ❌ 错误:字符串值被解析为布尔值
myapp:
enabled: "true" # 被解析为字符串"true",不是布尔值true
# ✅ 正确
myapp:
enabled: true
解决方案:确保YAML值的类型正确,布尔值不要加引号。
错误3:Profile配置混乱
yaml
# ❌ 错误:Profile配置错误
spring:
profiles: dev
datasource:
url: jdbc:h2:mem:testdb
# ❌ 错误:Profile激活语法错误
spring:
profiles:
active: dev,prod # 应该用逗号分隔
# ✅ 正确写法1:使用application-dev.yml
# ✅ 正确写法2:在命令行激活
# java -jar app.jar --spring.profiles.active=dev,prod
解决方案:遵循Spring Boot的Profile规范,使用正确的配置方式。
错误4:环境变量注入失败
yaml
# ❌ 错误:环境变量不存在时无默认值
password: ${DB_PASSWORD} # 如果DB_PASSWORD不存在,启动失败
# ✅ 正确:提供默认值
password: ${DB_PASSWORD:default_password}
# ✅ 更好:强制要求环境变量
password: ${DB_PASSWORD:?MISSING_DB_PASSWORD}
解决方案:为环境变量提供合理的默认值,或在启动时验证。
五、 Spring Boot 3.2.x 新特性支持
5.1 虚拟线程支持
yaml
# Spring Boot 3.2 虚拟线程配置
spring:
threads:
virtual:
enabled: true # 启用虚拟线程
server:
tomcat:
threads:
# 使用虚拟线程
max: 200
min-spare: 10
# 虚拟线程任务执行器
task:
execution:
task-executor-type: virtual_threads
注意 :需要JDK 21+,并在启动时添加--enable-preview参数。
5.2 记录类型(Record)配置绑定
java
// JDK 16+ Record类型配置绑定
public record ServerConfig(String host, int port, boolean ssl) {}
@ConfigurationProperties(prefix = "app.server")
public record AppServerConfig(
@NotBlank String name,
@Valid List<ServerConfig> servers,
Duration timeout
) {}
对应配置:
yaml
app:
server:
name: api-server
timeout: 30s
servers:
- host: server1.example.com
port: 8080
ssl: true
- host: server2.example.com
port: 8081
ssl: false
六、 配置验证与测试
6.1 配置验证端点
创建配置验证的REST端点:
ConfigController.java:
java
package com.example.demo.controller;
import com.example.demo.config.AppConfigProperties;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 配置验证控制器
* 用于验证application.yml配置是否正确加载
*
* @author 专栏作者
* @version 1.0
* @since 2026-05-04
*/
@Slf4j
@Tag(name = "配置管理", description = "配置验证和管理接口")
@RestController
@RequestMapping("/api/config")
@RequiredArgsConstructor
public class ConfigController {
private final Environment environment;
private final AppConfigProperties appConfigProperties;
@Operation(summary = "获取应用配置", description = "获取当前应用的所有配置信息")
@GetMapping("/app")
public Map<String, Object> getAppConfig() {
log.debug("获取应用配置");
Map<String, Object> config = new HashMap<>();
config.put("applicationName", environment.getProperty("spring.application.name"));
config.put("serverPort", environment.getProperty("server.port"));
config.put("activeProfiles", environment.getActiveProfiles());
config.put("customConfig", appConfigProperties.getAllConfig());
return config;
}
@Operation(summary = "验证数据库配置", description = "验证数据库连接配置")
@GetMapping("/db")
public Map<String, Object> getDbConfig() {
log.debug("获取数据库配置");
Map<String, Object> dbConfig = new HashMap<>();
dbConfig.put("url", environment.getProperty("spring.datasource.url"));
dbConfig.put("driverClass", environment.getProperty("spring.datasource.driver-class-name"));
dbConfig.put("maxPoolSize", environment.getProperty("spring.datasource.hikari.maximum-pool-size"));
dbConfig.put("minIdle", environment.getProperty("spring.datasource.hikari.minimum-idle"));
return dbConfig;
}
@Operation(summary = "验证Redis配置", description = "验证Redis连接配置")
@GetMapping("/redis")
public Map<String, Object> getRedisConfig() {
log.debug("获取Redis配置");
Map<String, Object> redisConfig = new HashMap<>();
redisConfig.put("host", environment.getProperty("spring.data.redis.host"));
redisConfig.put("port", environment.getProperty("spring.data.redis.port"));
redisConfig.put("database", environment.getProperty("spring.data.redis.database"));
redisConfig.put("maxActive", environment.getProperty("spring.data.redis.lettuce.pool.max-active"));
return redisConfig;
}
@Operation(summary = "获取所有配置", description = "获取所有环境变量和系统属性")
@GetMapping("/all")
public Map<String, Object> getAllConfig() {
log.debug("获取所有配置");
Map<String, Object> allConfig = new HashMap<>();
// 系统属性
Map<String, String> systemProps = new HashMap<>();
systemProps.put("java.version", System.getProperty("java.version"));
systemProps.put("user.dir", System.getProperty("user.dir"));
systemProps.put("os.name", System.getProperty("os.name"));
allConfig.put("systemProperties", systemProps);
// 环境变量
Map<String, String> envVars = new HashMap<>();
envVars.put("JAVA_HOME", System.getenv("JAVA_HOME"));
envVars.put("PATH", System.getenv("PATH"));
allConfig.put("environmentVariables", envVars);
// Spring配置
Map<String, String> springConfig = new HashMap<>();
springConfig.put("spring.application.name", environment.getProperty("spring.application.name"));
springConfig.put("server.port", environment.getProperty("server.port"));
springConfig.put("spring.profiles.active", String.join(",", environment.getActiveProfiles()));
allConfig.put("springConfig", springConfig);
return allConfig;
}
}
访问 /api/config/app 的响应示例:
json
{
"applicationName": "spring-boot-demo",
"serverPort": "8080",
"activeProfiles": [],
"customConfig": {
"config": {
"version": "1.0.0",
"uploadPath": "/data/uploads",
"tempPath": "/tmp"
},
"security": {
"jwt": {
"secret": "your-jwt-secret-key-change-in-production",
"expiration": 86400,
"header": "Authorization",
"tokenPrefix": "Bearer "
},
"encryption": {
"aesKey": "default-aes-key-change-me"
}
},
"business": {
"page": {
"maxSize": 100,
"defaultSize": 20
},
"cache": {
"userInfoTtl": 300,
"orderInfoTtl": 600
},
"retry": {
"maxAttempts": 3,
"backoffDelay": 1000
}
}
}
}
七、 配置文件模板下载
为方便快速开始,这里提供一个完整的配置文件模板:
[application-template.yml]
yaml
# ============================================
# Spring Boot 3.2.x Application Configuration Template
# 作者:xxxxxx
# 版本:1.0.0
# 日期:2026-05-04
# ============================================
# 基础配置
server:
port: 8080
servlet:
context-path: /
compression:
enabled: true
spring:
application:
name: ${APP_NAME:my-application}
profiles:
active: @activatedProperties@
# 数据源配置
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:appdb}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
username: ${DB_USER:root}
password: ${DB_PASS:password}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
# Redis配置
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASS:}
lettuce:
pool:
max-active: 20
# 文件上传
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
# SpringDoc配置
springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: true
# 日志配置
logging:
level:
root: INFO
com.example: DEBUG
file:
name: logs/${spring.application.name}.log
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
# 自定义配置
app:
config:
version: 1.0.0
security:
jwt:
secret: ${JWT_SECRET:change-me-in-production}
business:
page:
max-size: 100
记住配置管理的黄金法则:配置应该与代码分离,环境特定的配置应该从代码中完全移除。
在实际项目中,随着服务规模的扩大,建议考虑使用配置中心(如Nacos、Apollo、Consul)来实现配置的集中管理和动态刷新。
相关资源: