本文深入剖析Spring Boot配置体系,包括多环境配置、属性绑定、SpEL表达式、profile隔离策略以及敏感信息加密,结合企业实践讲解配置最佳实践。
2.1 application.yml多环境配置
为什么需要多环境配置?
在软件开发生命周期中,一个应用通常需要部署到多个环境:
| 环境 | 用途 | 特点 | 配置差异 |
|---|---|---|---|
| dev(开发) | 本地开发调试 | 数据可随意修改 | 本地数据库、调试日志、mock服务 |
| test(测试) | 功能测试验证 | 模拟生产数据 | 测试数据库、详细日志 |
| uat(预发布) | 用户验收测试 | 接近生产环境 | 预发数据库、部分真实接口 |
| prod(生产) | 正式对外服务 | 真实数据、高可用 | 生产数据库、错误日志、真实接口 |
不同环境配置差异示例:
yaml
# 数据库连接不同
dev: mysql://localhost:3306/demo_dev
test: mysql://192.168.1.100:3306/demo_test
prod: mysql://prod-db.company.com:3306/demo_prod
# 日志级别不同
dev: DEBUG # 查看详细日志
test: INFO # 记录关键信息
prod: WARN # 只记录警告和错误
# 第三方服务不同
dev: mock服务(本地模拟)
test: 测试环境API
prod: 生产环境API
Spring Boot多环境配置方案
Spring Boot提供了profile机制实现多环境配置,有两种常用方案:
方案一:多文件方式(推荐)
src/main/resources/
├── application.yml # 主配置文件(公共配置)
├── application-dev.yml # 开发环境配置
├── application-test.yml # 测试环境配置
├── application-uat.yml # 预发布环境配置
└── application-prod.yml # 生产环境配置
方案二:单文件多profile方式
yaml
# application.yml
---
# 公共配置
spring:
application:
name: demo-service
---
# 开发环境
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:mysql://localhost:3306/demo_dev
---
# 生产环境
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-db:3306/demo_prod
两种方案对比:
| 维度 | 多文件方式 | 单文件方式 |
|---|---|---|
| 可读性 | 高,每个环境独立文件 | 低,所有配置混在一起 |
| 维护性 | 高,修改互不影响 | 低,容易改错环境 |
| 复杂度 | 低,结构清晰 | 高,需要---分隔 |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
企业实践推荐:使用多文件方式。
主配置文件设计
application.yml(公共配置)
yaml
# ==================== 应用基本信息 ====================
spring:
application:
name: demo-service
# 激活的profile(通过启动参数或环境变量指定)
profiles:
active: @spring.profiles.active@ # Maven占位符,打包时替换
# Jackson序列化配置(所有环境通用)
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
default-property-inclusion: non_null # null值不序列化
serialization:
write-dates-as-timestamps: false # 时间格式化为字符串
# ==================== MyBatis-Plus配置 ====================
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
db-config:
id-type: auto # 主键自增
logic-delete-field: deleted # 逻辑删除字段
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
# ==================== Server配置 ====================
server:
port: 8080
servlet:
context-path: /api
tomcat:
uri-encoding: UTF-8
threads:
max: 200 # 最大线程数
min-spare: 10 # 最小空闲线程
max-connections: 10000 # 最大连接数
accept-count: 100 # 等待队列长度
# ==================== 日志配置 ====================
logging:
level:
root: INFO
com.company: DEBUG # 本项目包路径
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
关键设计点:
1. Maven占位符
yaml
spring:
profiles:
active: @spring.profiles.active@
配合pom.xml使用:
xml
<profiles>
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault> <!-- 默认激活 -->
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
打包时指定环境:
bash
# 打包dev环境
mvn clean package -P dev
# 打包生产环境
mvn clean package -P prod
效果: 打包后的jar包中,application.yml里的@spring.profiles.active@会被替换为实际值(dev或prod)。
2. 公共配置提取
将所有环境通用的配置放在application.yml,避免重复配置:
- 应用名称
- Jackson序列化配置
- MyBatis-Plus全局配置
- 日志格式
差异化配置放在各环境文件中:
- 数据库连接
- Redis连接
- 第三方API地址
- 日志级别
开发环境配置
application-dev.yml
yaml
# ==================== 数据源配置 ====================
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# Druid连接池配置
druid:
initial-size: 5 # 初始连接数
min-idle: 5 # 最小空闲连接
max-active: 20 # 最大活跃连接(开发环境不需要太多)
max-wait: 60000 # 获取连接最大等待时间(ms)
validation-query: SELECT 1
test-while-idle: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
# ==================== Redis配置 ====================
redis:
host: localhost
port: 6379
password: # 开发环境无密码
database: 0
timeout: 5000
lettuce:
pool:
max-active: 8 # 开发环境连接池较小
max-wait: -1
max-idle: 8
min-idle: 0
# ==================== 日志配置 ====================
logging:
level:
root: INFO
com.company: DEBUG # 开发环境打印DEBUG日志
com.company.module.order.dal.mapper: DEBUG # 打印SQL日志
file:
name: ./logs/demo-dev.log # 日志文件路径
# ==================== 第三方服务配置 ====================
third-party:
payment:
api-url: http://localhost:9001/mock/payment # Mock支付服务
app-key: dev-app-key
app-secret: dev-app-secret
timeout: 30000
sms:
api-url: http://localhost:9002/mock/sms # Mock短信服务
access-key: dev-access-key
access-secret: dev-access-secret
# ==================== 功能开关配置 ====================
feature:
cache-enabled: true # 开启缓存(方便测试缓存功能)
async-enabled: true # 开启异步(测试异步任务)
rate-limit-enabled: false # 关闭限流(方便调试)
生产环境配置
application-prod.yml
yaml
# ==================== 数据源配置 ====================
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://prod-db-master.company.com:3306/demo_prod?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true
username: ${DB_USERNAME} # 从环境变量读取(安全)
password: ${DB_PASSWORD} # 从环境变量读取(安全)
# Druid连接池配置(生产环境优化)
druid:
initial-size: 10 # 初始连接数
min-idle: 10 # 最小空闲连接
max-active: 100 # 最大活跃连接(生产环境需要更多)
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false # 获取连接时不测试(提升性能)
test-on-return: false
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
# 监控配置(生产环境开启)
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: ${DRUID_USERNAME}
login-password: ${DRUID_PASSWORD}
# ==================== Redis配置 ====================
redis:
host: ${REDIS_HOST} # 从环境变量读取
port: ${REDIS_PORT:6379} # 默认6379
password: ${REDIS_PASSWORD}
database: 0
timeout: 5000
lettuce:
pool:
max-active: 50 # 生产环境连接池较大
max-wait: 10000
max-idle: 20
min-idle: 5
cluster:
refresh:
adaptive: true # 自适应刷新拓扑
period: 30000
# ==================== 日志配置 ====================
logging:
level:
root: WARN # 生产环境只记录警告
com.company: INFO # 业务日志INFO级别
file:
name: /app/logs/demo-prod.log
max-size: 100MB # 单文件最大100MB
max-history: 30 # 保留30天
total-size-cap: 10GB # 总大小限制10GB
# ==================== 第三方服务配置 ====================
third-party:
payment:
api-url: https://api.payment.company.com # 真实支付服务
app-key: ${PAYMENT_APP_KEY}
app-secret: ${PAYMENT_APP_SECRET}
timeout: 30000
retry-times: 3 # 重试3次
sms:
api-url: https://api.sms.company.com # 真实短信服务
access-key: ${SMS_ACCESS_KEY}
access-secret: ${SMS_ACCESS_SECRET}
max-send-per-day: 1000 # 每天最多发送1000条
# ==================== 功能开关配置 ====================
feature:
cache-enabled: true # 生产环境必须开启缓存
async-enabled: true # 开启异步提升性能
rate-limit-enabled: true # 生产环境必须限流
# ==================== 监控配置 ====================
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus # 只暴露必要端点
metrics:
export:
prometheus:
enabled: true
配置优先级与覆盖规则
Spring Boot配置加载优先级(从高到低):
1. 命令行参数
java -jar app.jar --server.port=9090
2. 操作系统环境变量
export SERVER_PORT=9090
3. application-{profile}.yml(激活的profile配置)
application-prod.yml
4. application.yml(主配置文件)
5. @PropertySource注解加载的配置文件
6. 默认值(@Value注解的defaultValue)
覆盖规则示例:
yaml
# application.yml
server:
port: 8080
tomcat:
max-threads: 200
# application-prod.yml
server:
port: 80 # 覆盖端口
tomcat:
max-threads: 500 # 覆盖线程数
accept-count: 100 # 新增配置(不会删除application.yml中的其他配置)
最终生效的配置(prod环境):
yaml
server:
port: 80 # 来自application-prod.yml
tomcat:
max-threads: 500 # 来自application-prod.yml
accept-count: 100 # 来自application-prod.yml
动态切换环境的方式
方式1:启动参数指定(推荐)
bash
# 启动时指定profile
java -jar demo-service.jar --spring.profiles.active=prod
# 同时指定多个profile
java -jar demo-service.jar --spring.profiles.active=prod,redis-cluster
方式2:环境变量指定
bash
# Linux/Mac
export SPRING_PROFILES_ACTIVE=prod
java -jar demo-service.jar
# Windows
set SPRING_PROFILES_ACTIVE=prod
java -jar demo-service.jar
方式3:JVM参数指定
bash
java -Dspring.profiles.active=prod -jar demo-service.jar
方式4:IDEA运行配置
Run -> Edit Configurations -> Active profiles: prod
生产环境最佳实践:
bash
# 使用启动脚本
#!/bin/bash
APP_NAME=demo-service
PROFILE=prod
JVM_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC"
java $JVM_OPTS \
-Dspring.profiles.active=$PROFILE \
-jar $APP_NAME.jar \
>> /app/logs/startup.log 2>&1 &
2.2 @ConfigurationProperties属性绑定
为什么需要属性绑定?
在传统的配置读取方式中,需要使用@Value注解逐个读取配置:
java
// 传统方式:每个字段都要@Value注解,繁琐且容易出错
@Component
public class PaymentConfig {
@Value("${third-party.payment.api-url}")
private String apiUrl;
@Value("${third-party.payment.app-key}")
private String appKey;
@Value("${third-party.payment.app-secret}")
private String appSecret;
@Value("${third-party.payment.timeout}")
private Integer timeout;
// 10个配置就要写10个@Value...
}
痛点:
- 代码冗长:每个字段都要写@Value注解
- 容易出错:配置key写错了编译不报错,运行才发现
- 不支持复杂对象:嵌套对象、List、Map等难以绑定
- 无类型校验:配置值类型错误运行时才报错
@ConfigurationProperties的优势:
- 批量绑定:自动将配置绑定到对象属性
- 类型安全:编译期检查,配置错误立即发现
- 支持复杂类型:嵌套对象、集合、Map等
- 支持校验:配合@Validated进行参数校验
- IDE友好:配置文件中有智能提示
@ConfigurationProperties基本用法
1. 定义配置类
java
package com.company.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.*;
import java.time.Duration;
import java.util.List;
import java.util.Map;
/**
* 第三方服务配置
*
* <p>关键注解:
* - @ConfigurationProperties: 绑定配置前缀
* - @Component: 注册为Spring Bean
* - @Validated: 启用参数校验
*/
@Data
@Component
@ConfigurationProperties(prefix = "third-party")
@Validated
public class ThirdPartyProperties {
/** 支付服务配置 */
private PaymentConfig payment;
/** 短信服务配置 */
private SmsConfig sms;
/** OSS配置 */
private OssConfig oss;
/**
* 支付服务配置
*/
@Data
public static class PaymentConfig {
/** API地址(必填) */
@NotBlank(message = "支付API地址不能为空")
private String apiUrl;
/** 应用Key(必填) */
@NotBlank(message = "支付AppKey不能为空")
private String appKey;
/** 应用Secret(必填) */
@NotBlank(message = "支付AppSecret不能为空")
private String appSecret;
/** 超时时间(默认30秒) */
@Min(value = 1000, message = "超时时间不能小于1秒")
@Max(value = 300000, message = "超时时间不能大于5分钟")
private Integer timeout = 30000;
/** 重试次数(默认3次) */
@Min(value = 0, message = "重试次数不能为负数")
@Max(value = 5, message = "重试次数不能超过5次")
private Integer retryTimes = 3;
/** 是否启用(默认true) */
private Boolean enabled = true;
/** 支持的支付方式 */
private List<String> supportedMethods;
}
/**
* 短信服务配置
*/
@Data
public static class SmsConfig {
@NotBlank
private String apiUrl;
@NotBlank
private String accessKey;
@NotBlank
private String accessSecret;
/** 每天最大发送量 */
@Min(value = 1, message = "每天至少发送1条")
private Integer maxSendPerDay = 1000;
/** 发送间隔(使用Duration类型) */
private Duration sendInterval = Duration.ofSeconds(60);
/** 短信模板配置(Map类型) */
private Map<String, String> templates;
}
/**
* OSS配置
*/
@Data
public static class OssConfig {
@NotBlank
private String endpoint;
@NotBlank
private String accessKeyId;
@NotBlank
private String accessKeySecret;
@NotBlank
private String bucketName;
/** 允许的文件类型 */
private List<String> allowedExtensions;
/** 最大文件大小(单位:字节) */
@Min(1)
private Long maxFileSize = 10 * 1024 * 1024L; // 默认10MB
}
}
2. application.yml配置
yaml
third-party:
# 支付服务配置
payment:
api-url: https://api.payment.com
app-key: your-app-key
app-secret: your-app-secret
timeout: 30000
retry-times: 3
enabled: true
supported-methods:
- alipay
- wechat
- unionpay
# 短信服务配置
sms:
api-url: https://api.sms.com
access-key: your-access-key
access-secret: your-access-secret
max-send-per-day: 1000
send-interval: 60s # Duration类型自动解析
templates:
login: "验证码:{code},有效期5分钟"
register: "欢迎注册,验证码:{code}"
reset-password: "重置密码验证码:{code}"
# OSS配置
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com
access-key-id: your-key-id
access-key-secret: your-key-secret
bucket-name: demo-bucket
allowed-extensions:
- jpg
- png
- pdf
- docx
max-file-size: 10485760 # 10MB
3. 使用配置
java
package com.company.service;
import com.company.config.ThirdPartyProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentService {
// 直接注入配置对象
private final ThirdPartyProperties thirdPartyProperties;
public void createPayment(String orderNo, BigDecimal amount) {
// 获取支付配置
ThirdPartyProperties.PaymentConfig paymentConfig = thirdPartyProperties.getPayment();
// 使用配置
String apiUrl = paymentConfig.getApiUrl();
String appKey = paymentConfig.getAppKey();
Integer timeout = paymentConfig.getTimeout();
log.info("[创建支付] URL={}, AppKey={}, Timeout={}ms",
apiUrl, appKey, timeout);
// 调用支付接口...
if (paymentConfig.getEnabled()) {
callPaymentApi(apiUrl, appKey, orderNo, amount, timeout);
}
}
private void callPaymentApi(String apiUrl, String appKey,
String orderNo, BigDecimal amount, Integer timeout) {
// 实际调用支付API
}
}
复杂类型绑定
1. List类型绑定
yaml
# application.yml
app:
admin-users:
- name: admin
email: admin@company.com
roles:
- ADMIN
- USER
- name: operator
email: operator@company.com
roles:
- OPERATOR
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private List<AdminUser> adminUsers;
@Data
public static class AdminUser {
private String name;
private String email;
private List<String> roles;
}
}
2. Map类型绑定
yaml
# application.yml
app:
error-codes:
1001: "用户不存在"
1002: "密码错误"
1003: "账号已锁定"
rate-limits:
login: 5
register: 3
reset-password: 3
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
/** 错误码映射 */
private Map<Integer, String> errorCodes;
/** 接口限流配置 */
private Map<String, Integer> rateLimits;
}
3. Duration类型绑定
yaml
# application.yml
app:
cache-ttl: 30m # 30分钟
session-timeout: 2h # 2小时
token-expire: 7d # 7天
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Duration cacheTtl; // PT30M
private Duration sessionTimeout; // PT2H
private Duration tokenExpire; // P7D
}
支持的Duration格式:
10s= 10秒5m= 5分钟2h= 2小时3d= 3天
4. DataSize类型绑定
yaml
# application.yml
app:
max-upload-size: 10MB
buffer-size: 1KB
cache-size: 500MB
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private DataSize maxUploadSize; // 10485760 bytes
private DataSize bufferSize; // 1024 bytes
private DataSize cacheSize; // 524288000 bytes
}
配置校验
使用JSR-303注解进行配置校验:
java
@Data
@Component
@ConfigurationProperties(prefix = "app")
@Validated // 必须添加此注解才能启用校验
public class AppProperties {
/** 应用名称(必填,长度3-50) */
@NotBlank(message = "应用名称不能为空")
@Size(min = 3, max = 50, message = "应用名称长度必须在3-50之间")
private String name;
/** 应用版本(必填,符合版本号格式) */
@NotBlank
@Pattern(regexp = "^\\d+\\.\\d+\\.\\d+$", message = "版本号格式错误,正确格式:1.0.0")
private String version;
/** 端口号(范围1024-65535) */
@Min(value = 1024, message = "端口号不能小于1024")
@Max(value = 65535, message = "端口号不能大于65535")
private Integer port;
/** 邮箱地址(必填,符合邮箱格式) */
@NotBlank
@Email(message = "邮箱格式错误")
private String contactEmail;
/** 官网地址(必填,符合URL格式) */
@NotBlank
@URL(message = "官网地址格式错误")
private String website;
/** 管理员列表(至少1个) */
@NotEmpty(message = "至少需要配置一个管理员")
private List<String> adminEmails;
/** 嵌套对象校验 */
@Valid // 嵌套对象需要@Valid注解
@NotNull
private DatabaseConfig database;
@Data
public static class DatabaseConfig {
@NotBlank
private String url;
@NotBlank
@Size(min = 4, max = 20)
private String username;
@NotBlank
@Size(min = 6, max = 50)
private String password;
}
}
配置错误时的异常:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException:
Failed to bind properties under 'app' to com.company.config.AppProperties failed:
Property: app.name
Value: ab
Reason: 应用名称长度必须在3-50之间
Property: app.version
Value: 1.0
Reason: 版本号格式错误,正确格式:1.0.0
松散绑定(Relaxed Binding)
Spring Boot支持松散绑定,配置文件中的key可以使用多种格式:
yaml
# 以下配置都能绑定到 appName 字段
app:
app-name: demo # 短横线(推荐)
appName: demo # 驼峰
app_name: demo # 下划线
APP_NAME: demo # 大写(环境变量)
java
@Data
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String appName; // 都能绑定到这个字段
}
绑定规则:
| 配置格式 | 示例 | 适用场景 |
|---|---|---|
| 短横线(kebab-case) | app-name |
yml/properties文件(推荐) |
| 驼峰(camelCase) | appName |
yml/properties文件 |
| 下划线(snake_case) | app_name |
yml/properties文件 |
| 大写下划线 | APP_NAME |
环境变量 |
@ConfigurationProperties vs @Value
| 对比维度 | @ConfigurationProperties | @Value |
|---|---|---|
| 适用场景 | 批量配置绑定 | 单个配置读取 |
| 松散绑定 | 支持 | 不支持(必须精确匹配) |
| SpEL表达式 | 不支持 | 支持 |
| JSR-303校验 | 支持 | 不支持 |
| 复杂类型 | 支持(List、Map、嵌套对象) | 不支持 |
| 元数据支持 | 支持(IDE智能提示) | 不支持 |
| 类型安全 | 编译期检查 | 运行时检查 |
| 推荐度 | ⭐⭐⭐⭐⭐(复杂配置) | ⭐⭐⭐(简单配置) |
使用建议:
- 配置类 :使用
@ConfigurationProperties - 单个简单配置 :使用
@Value - 需要SpEL表达式 :使用
@Value
2.3 @Value注解与SpEL表达式
@Value基本用法
java
@Component
public class AppConfig {
/** 基本用法:读取配置值 */
@Value("${app.name}")
private String appName;
/** 提供默认值:配置不存在时使用默认值 */
@Value("${app.version:1.0.0}")
private String appVersion;
/** 读取系统属性 */
@Value("${java.home}")
private String javaHome;
/** 读取环境变量 */
@Value("${PATH}")
private String systemPath;
}
SpEL表达式详解
SpEL(Spring Expression Language)是Spring的表达式语言,功能强大。
1. 字面量
java
@Component
public class SpELDemo {
/** 字符串字面量 */
@Value("#{'Hello World'}")
private String str1;
/** 数字字面量 */
@Value("#{100}")
private Integer num1;
/** 布尔字面量 */
@Value("#{true}")
private Boolean bool1;
/** 数组/列表字面量 */
@Value("#{{1,2,3,4,5}}")
private List<Integer> numbers;
}
2. 算术运算
java
@Component
public class SpELArithmetic {
/** 加法 */
@Value("#{10 + 20}")
private Integer sum; // 30
/** 减法 */
@Value("#{100 - 30}")
private Integer diff; // 70
/** 乘法 */
@Value("#{5 * 6}")
private Integer product; // 30
/** 除法 */
@Value("#{100 / 4}")
private Integer quotient; // 25
/** 取模 */
@Value("#{10 % 3}")
private Integer remainder; // 1
/** 幂运算 */
@Value("#{2 ^ 10}")
private Integer power; // 1024
}
3. 关系运算
java
@Component
public class SpELComparison {
@Value("#{10 > 5}")
private Boolean gt; // true
@Value("#{10 >= 10}")
private Boolean gte; // true
@Value("#{5 < 3}")
private Boolean lt; // false
@Value("#{5 <= 5}")
private Boolean lte; // true
@Value("#{10 == 10}")
private Boolean eq; // true
@Value("#{10 != 5}")
private Boolean ne; // true
}
4. 逻辑运算
java
@Component
public class SpELLogical {
/** 逻辑与 */
@Value("#{true and false}")
private Boolean and; // false
/** 逻辑或 */
@Value("#{true or false}")
private Boolean or; // true
/** 逻辑非 */
@Value("#{not true}")
private Boolean not; // false
/** 组合表达式 */
@Value("#{(10 > 5) and (20 < 30)}")
private Boolean combined; // true
}
5. 三元运算符
java
@Component
public class SpELTernary {
@Value("${app.env}")
private String env;
/** 三元运算符 */
@Value("#{env == 'prod' ? 'https://api.prod.com' : 'http://api.dev.com'}")
private String apiUrl;
/** Elvis运算符(简化的三元) */
@Value("#{env ?: 'dev'}") // env为null时使用'dev'
private String environment;
/** 安全导航运算符 */
@Value("#{user?.name}") // user为null时不抛异常,返回null
private String userName;
}
6. 正则表达式
java
@Component
public class SpELRegex {
@Value("${user.email}")
private String email;
/** 正则匹配 */
@Value("#{email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'}")
private Boolean isValidEmail;
@Value("${user.phone}")
private String phone;
/** 手机号验证 */
@Value("#{phone matches '^1[3-9]\\d{9}$'}")
private Boolean isValidPhone;
}
7. 调用方法
java
@Component
public class SpELMethod {
@Value("${user.name}")
private String userName;
/** 调用String方法 */
@Value("#{userName.toUpperCase()}")
private String upperName;
@Value("#{userName.length()}")
private Integer nameLength;
@Value("#{userName.substring(0, 3)}")
private String namePrefix;
/** 调用静态方法 */
@Value("#{T(java.lang.Math).random()}")
private Double randomNum;
@Value("#{T(java.lang.Math).max(10, 20)}")
private Integer maxNum; // 20
@Value("#{T(java.time.LocalDate).now()}")
private LocalDate currentDate;
}
8. 引用Bean属性
java
@Component("systemConfig")
public class SystemConfig {
private String appName = "Demo System";
private String version = "1.0.0";
// getter/setter
}
@Component
public class SpELBeanRef {
/** 引用Bean的属性 */
@Value("#{systemConfig.appName}")
private String appName;
@Value("#{systemConfig.version}")
private String version;
/** 引用并调用方法 */
@Value("#{systemConfig.appName.toUpperCase()}")
private String upperAppName;
}
9. 集合操作
java
@Component
public class SpELCollection {
/** 过滤集合(selection) */
@Value("#{numbers.?[#this > 5]}") // 筛选大于5的元素
private List<Integer> filtered;
/** 映射集合(projection) */
@Value("#{users.![name]}") // 提取所有用户的name字段
private List<String> userNames;
/** 集合第一个元素 */
@Value("#{users[0].name}")
private String firstName;
/** Map取值 */
@Value("#{configs['database.url']}")
private String dbUrl;
}
SpEL实战场景
场景1:根据环境动态配置
java
@Component
public class DynamicConfig {
@Value("${spring.profiles.active}")
private String profile;
/** 根据环境选择不同的API地址 */
@Value("#{profile == 'prod' ? 'https://api.prod.com' : 'http://localhost:8080'}")
private String apiUrl;
/** 根据环境选择不同的日志级别 */
@Value("#{profile == 'prod' ? 'WARN' : 'DEBUG'}")
private String logLevel;
/** 根据环境决定是否启用缓存 */
@Value("#{profile == 'prod' or profile == 'uat'}")
private Boolean cacheEnabled;
}
场景2:配置计算
java
@Component
public class CalculatedConfig {
@Value("${thread.pool.core-size:10}")
private Integer coreSize;
/** 最大线程数 = 核心线程数 * 2 */
@Value("#{coreSize * 2}")
private Integer maxSize;
/** 队列容量 = 核心线程数 * 10 */
@Value("#{coreSize * 10}")
private Integer queueCapacity;
@Value("${cache.ttl.seconds:300}")
private Long ttlSeconds;
/** 将秒转换为毫秒 */
@Value("#{ttlSeconds * 1000}")
private Long ttlMillis;
}
场景3:配置校验
java
@Component
public class ValidatedConfig {
@Value("${app.version}")
private String version;
/** 校验版本号格式 */
@Value("#{version matches '^\\d+\\.\\d+\\.\\d+$' ? version : '1.0.0'}")
private String validatedVersion;
@Value("${server.port}")
private Integer port;
/** 校验端口范围 */
@Value("#{port >= 1024 and port <= 65535 ? port : 8080}")
private Integer validatedPort;
}
@Value的最佳实践
1. 提供默认值
java
// ✅ 好习惯:提供默认值
@Value("${app.timeout:30000}")
private Integer timeout;
// ❌ 不推荐:配置不存在时启动失败
@Value("${app.timeout}")
private Integer timeout;
2. 使用静态常量定义配置key
java
public class ConfigKeys {
public static final String APP_NAME = "${app.name}";
public static final String APP_VERSION = "${app.version:1.0.0}";
}
@Component
public class AppService {
@Value(ConfigKeys.APP_NAME)
private String appName;
@Value(ConfigKeys.APP_VERSION)
private String appVersion;
}
3. 复杂配置使用@ConfigurationProperties
java
// ❌ 不推荐:大量@Value注解
@Value("${db.url}")
private String dbUrl;
@Value("${db.username}")
private String dbUsername;
@Value("${db.password}")
private String dbPassword;
// ... 还有10个配置
// ✅ 推荐:使用@ConfigurationProperties
@ConfigurationProperties(prefix = "db")
public class DatabaseProperties {
private String url;
private String username;
private String password;
// ... 其他配置
}
4. 避免在@Value中使用复杂SpEL
java
// ❌ 不推荐:逻辑太复杂
@Value("#{profile == 'prod' ? (region == 'cn' ? 'https://api-cn.prod.com' : 'https://api-us.prod.com') : 'http://localhost:8080'}")
private String apiUrl;
// ✅ 推荐:使用Java代码实现复杂逻辑
@PostConstruct
public void init() {
if ("prod".equals(profile)) {
apiUrl = "cn".equals(region) ? "https://api-cn.prod.com" : "https://api-us.prod.com";
} else {
apiUrl = "http://localhost:8080";
}
}
2.4 profile配置隔离策略
profile激活机制
Spring Boot支持多种方式激活profile:
1. 配置文件激活(application.yml)
yaml
spring:
profiles:
active: dev # 激活dev profile
2. 命令行参数激活
bash
java -jar app.jar --spring.profiles.active=prod
3. 环境变量激活
bash
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar
4. JVM系统属性激活
bash
java -Dspring.profiles.active=prod -jar app.jar
5. Maven profile激活
xml
<profiles>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
bash
mvn clean package -P prod
优先级(从高到低):
命令行参数 > JVM系统属性 > 环境变量 > 配置文件
多profile同时激活
同时激活多个profile:
bash
# 激活prod和redis-cluster两个profile
java -jar app.jar --spring.profiles.active=prod,redis-cluster
yaml
# application-prod.yml(基础配置)
spring:
datasource:
url: jdbc:mysql://prod-db:3306/demo
# application-redis-cluster.yml(Redis集群配置)
spring:
redis:
cluster:
nodes:
- 192.168.1.1:7000
- 192.168.1.2:7000
- 192.168.1.3:7000
最终生效: 两个配置文件都会加载,配置合并。
profile组合(Profile Groups)
Spring Boot 2.4+支持profile组合:
yaml
# application.yml
spring:
profiles:
group:
prod:
- prod-base
- prod-db
- prod-redis
- prod-mq
dev:
- dev-base
- dev-db
使用:
bash
# 只需激活prod,自动激活prod-base、prod-db、prod-redis、prod-mq
java -jar app.jar --spring.profiles.active=prod
@Profile注解
根据profile条件装配Bean:
java
/**
* 开发环境配置
* 只在dev profile激活时生效
*/
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource devDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/demo_dev");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setMaximumPoolSize(10); // 开发环境小连接池
return dataSource;
}
@Bean
public CacheManager devCacheManager() {
// 开发环境使用简单缓存
return new ConcurrentMapCacheManager();
}
}
/**
* 生产环境配置
* 只在prod profile激活时生效
*/
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource prodDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://prod-db:3306/demo_prod");
dataSource.setUsername(System.getenv("DB_USERNAME"));
dataSource.setPassword(System.getenv("DB_PASSWORD"));
dataSource.setMaximumPoolSize(100); // 生产环境大连接池
return dataSource;
}
@Bean
public CacheManager prodCacheManager(RedisConnectionFactory factory) {
// 生产环境使用Redis缓存
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
@Profile支持表达式:
java
// 在dev或test环境生效
@Configuration
@Profile({"dev", "test"})
public class NonProdConfig {
}
// 不在prod环境生效
@Configuration
@Profile("!prod")
public class NonProdConfig {
}
// 在dev环境且debug模式生效
@Configuration
@Profile("dev & debug")
public class DevDebugConfig {
}
// 在dev或test,但不在local环境生效
@Configuration
@Profile("(dev | test) & !local")
public class ComplexConfig {
}
profile配置覆盖策略
配置文件加载顺序:
1. application.yml(主配置)
2. application-{profile}.yml(profile配置)
3. 命令行参数
示例:
yaml
# application.yml(所有环境通用)
server:
port: 8080
tomcat:
max-threads: 200
accept-count: 100
logging:
level:
root: INFO
yaml
# application-prod.yml(生产环境覆盖)
server:
port: 80 # 覆盖端口
tomcat:
max-threads: 500 # 覆盖最大线程数
# accept-count继承主配置的100
logging:
level:
root: WARN # 覆盖日志级别
最终生效配置(prod环境):
yaml
server:
port: 80 # 来自application-prod.yml
tomcat:
max-threads: 500 # 来自application-prod.yml
accept-count: 100 # 来自application.yml(未被覆盖)
logging:
level:
root: WARN # 来自application-prod.yml
配置文件位置优先级
Spring Boot会从以下位置加载配置文件(优先级从高到低):
1. file:./config/ (当前目录/config子目录)
2. file:./ (当前目录)
3. classpath:/config/ (classpath下config目录)
4. classpath:/ (classpath根目录)
实战场景:
# 生产环境部署结构
/app/
├── demo-service.jar # 应用jar包
├── application-prod.yml # 外部配置文件(优先级最高)
└── config/
└── custom.yml # 自定义配置
启动:
bash
java -jar demo-service.jar --spring.profiles.active=prod
加载顺序:
- 先加载jar包内的
application.yml(classpath:/) - 再加载jar包外的
application-prod.yml(file:./) - 外部配置覆盖jar包内配置
优点:
- jar包保持不变
- 通过外部配置文件调整配置
- 无需重新打包
2.5 配置加密与敏感信息处理
为什么需要配置加密?
配置文件中的敏感信息:
yaml
# ❌ 明文存储(安全隐患)
spring:
datasource:
username: root
password: MyPassword123! # 密码明文
redis:
password: Redis@2024 # Redis密码明文
third-party:
payment:
app-secret: sk_live_51Hxxx... # 支付密钥明文
sms:
access-secret: AbCd1234EfGh5678 # 短信密钥明文
安全风险:
- 代码泄露风险:配置文件提交到Git,密码泄露
- 运维风险:运维人员可以看到所有密码
- 审计困难:无法追踪谁访问了敏感配置
- 合规问题:不符合安全审计要求(如ISO27001)
配置加密方案
方案1:Jasypt加密(推荐)
Jasypt是一个Java加密库,Spring Boot有对应的starter。
1. 引入依赖
xml
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
2. 配置加密密钥
yaml
# application.yml
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD} # 从环境变量读取
algorithm: PBEWithMD5AndDES # 加密算法
iv-generator-classname: org.jasypt.iv.NoIvGenerator
3. 加密配置值
java
import org.jasypt.encryption.StringEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class ConfigEncryptor implements CommandLineRunner {
@Autowired
private StringEncryptor encryptor;
@Override
public void run(String... args) {
// 加密敏感配置
String dbPassword = "MyPassword123!";
String encrypted = encryptor.encrypt(dbPassword);
System.out.println("加密后:" + encrypted);
// 输出:oQw9K3xXzP7Yx8jF2nB4vA==
}
}
4. 使用加密配置
yaml
spring:
datasource:
username: root
password: ENC(oQw9K3xXzP7Yx8jF2nB4vA==) # ENC()包裹加密值
redis:
password: ENC(aB3cD5eF7gH9iJ1kL3mN5oP7==)
third-party:
payment:
app-secret: ENC(qR9sT1uV3wX5yZ7aB9cD1eF3==)
5. 启动应用
bash
# 通过环境变量传递解密密钥
export JASYPT_ENCRYPTOR_PASSWORD=my-secret-key
java -jar app.jar
优点:
- ✅ 配置文件中不再有明文密码
- ✅ 解密密钥通过环境变量传递,不出现在代码中
- ✅ 支持多种加密算法
- ✅ 对应用代码无侵入
方案2:环境变量(推荐)
yaml
# application.yml(不包含敏感信息)
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD}
启动脚本:
bash
#!/bin/bash
# start.sh
# 设置环境变量
export DB_HOST=prod-db.company.com
export DB_PORT=3306
export DB_NAME=demo_prod
export DB_USERNAME=app_user
export DB_PASSWORD=SecurePassword123!
export REDIS_HOST=redis-cluster.company.com
export REDIS_PORT=6379
export REDIS_PASSWORD=RedisSecure456!
# 启动应用
java -jar demo-service.jar
或使用Docker:
yaml
# docker-compose.yml
version: '3'
services:
app:
image: demo-service:latest
environment:
- DB_HOST=prod-db
- DB_USERNAME=app_user
- DB_PASSWORD=SecurePassword123!
- REDIS_HOST=redis
- REDIS_PASSWORD=RedisSecure456!
优点:
- ✅ 配置文件完全不包含敏感信息
- ✅ 每个环境可以设置不同的环境变量
- ✅ 符合12-Factor App原则
- ✅ 容器化部署友好
方案3:配置中心(企业级推荐)
使用Nacos、Apollo、Spring Cloud Config等配置中心:
yaml
# bootstrap.yml(只包含配置中心地址)
spring:
cloud:
nacos:
config:
server-addr: ${NACOS_SERVER:nacos.company.com:8848}
namespace: ${NACOS_NAMESPACE:prod}
group: DEFAULT_GROUP
username: ${NACOS_USERNAME}
password: ${NACOS_PASSWORD}
配置存储在Nacos:
yaml
# 在Nacos Web界面配置(支持加密)
spring.datasource.url=jdbc:mysql://prod-db:3306/demo_prod
spring.datasource.username=app_user
spring.datasource.password=SecurePassword123! # Nacos内部加密存储
优点:
- ✅ 配置集中管理
- ✅ 支持配置加密
- ✅ 支持配置动态刷新
- ✅ 权限控制(不同角色看到不同配置)
- ✅ 配置变更审计
- ✅ 配置版本管理
敏感信息处理最佳实践
1. 分离敏感配置
src/main/resources/
├── application.yml # 非敏感配置(可以提交Git)
├── application-dev.yml # 开发环境(可以提交Git)
├── application-prod.yml # 生产环境(不提交Git,通过配置中心管理)
└── .gitignore # 忽略敏感文件
.gitignore:
# 忽略生产环境配置
application-prod.yml
application-uat.yml
# 忽略包含敏感信息的配置
*-secret.yml
*.key
*.pem
2. 使用占位符
yaml
# application-prod.template.yml(模板文件,可提交Git)
spring:
datasource:
url: jdbc:mysql://prod-db:3306/demo_prod
username: ${DB_USERNAME} # 占位符
password: ${DB_PASSWORD} # 占位符
redis:
host: ${REDIS_HOST}
password: ${REDIS_PASSWORD}
部署时替换:
bash
# 生成实际配置文件
envsubst < application-prod.template.yml > application-prod.yml
3. 密钥管理
java
/**
* 密钥配置
* 敏感信息通过专门的密钥管理服务获取
*/
@Configuration
public class SecretConfig {
@Value("${secret.manager.url}")
private String secretManagerUrl;
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// 从密钥管理服务获取数据库密码
String dbPassword = getSecretFromVault("database/prod/password");
dataSource.setJdbcUrl("jdbc:mysql://prod-db:3306/demo_prod");
dataSource.setUsername("app_user");
dataSource.setPassword(dbPassword); // 不在配置文件中
return dataSource;
}
/**
* 从Vault/KMS获取密钥
*/
private String getSecretFromVault(String secretPath) {
// 调用密钥管理服务API
// 例如:HashiCorp Vault、AWS KMS、Azure Key Vault
RestTemplate restTemplate = new RestTemplate();
String url = secretManagerUrl + "/v1/secret/data/" + secretPath;
SecretResponse response = restTemplate.getForObject(url, SecretResponse.class);
return response.getData().getPassword();
}
}
4. 访问控制
yaml
# 配置中心ACL(访问控制列表)
nacos:
config:
database-password:
readers: # 只读权限
- ops-team
- dba-team
writers: # 读写权限
- admin-team
encryption: true # 强制加密
audit-log: true # 记录访问日志
5. 定期轮换密钥
java
/**
* 密钥轮换任务
* 定期更新敏感配置,降低泄露风险
*/
@Component
public class SecretRotationTask {
@Scheduled(cron = "0 0 2 1 * ?") // 每月1号凌晨2点执行
public void rotateSecrets() {
// 1. 生成新密码
String newPassword = generateSecurePassword();
// 2. 更新数据库密码
updateDatabasePassword(newPassword);
// 3. 更新配置中心
updateConfigCenter("spring.datasource.password", newPassword);
// 4. 通知运维团队
notifyOpsTeam("数据库密码已轮换");
}
}
配置安全检查清单
开发阶段:
- 使用
.gitignore防止敏感配置提交 - 使用配置模板文件(
.template.yml) - 敏感配置使用占位符(
${VAR}) - Code Review检查是否有硬编码密码
测试阶段:
- 验证加密配置能正常解密
- 验证环境变量注入是否正确
- 测试配置缺失时的降级策略
生产部署:
- 使用环境变量或配置中心
- 启用配置加密(Jasypt/Nacos加密)
- 配置访问权限控制
- 启用审计日志
- 定期轮换密钥
运维监控:
- 监控配置访问日志
- 告警异常配置读取
- 定期审计密钥使用情况
总结
Spring Boot配置体系通过多环境隔离、灵活绑定、表达式支持、安全加密构建了完整的配置管理方案:
| 技术 | 适用场景 | 核心价值 |
|---|---|---|
| 多环境配置 | dev/test/prod环境隔离 | 一套代码多环境部署 |
| @ConfigurationProperties | 批量配置绑定 | 类型安全、支持校验、IDE友好 |
| @Value + SpEL | 单个配置、动态计算 | 灵活表达式、运算支持 |
| profile机制 | 条件装配、配置覆盖 | 按需加载、配置分层 |
| 配置加密 | 敏感信息保护 | Jasypt加密、环境变量、配置中心 |
企业实践建议:
- 开发环境:使用本地配置文件,方便调试
- 测试环境:使用配置中心,模拟生产
- 生产环境:配置中心+环境变量+加密,三重保障
- 敏感信息:永不提交代码库,使用密钥管理服务
面试题精选
Q1: Spring Boot如何实现多环境配置?有哪些激活profile的方式?
参考答案:
多环境配置实现:
Spring Boot通过profile机制实现多环境配置,核心原理是根据激活的profile加载不同的配置文件。
配置文件命名规则:
application.yml # 主配置(所有环境通用)
application-{profile}.yml # profile配置(特定环境)
常见profile:
dev:开发环境test:测试环境uat:预发布环境prod:生产环境
激活profile的方式(优先级从高到低):
方式1:命令行参数(最高优先级)
bash
java -jar app.jar --spring.profiles.active=prod
方式2:JVM系统属性
bash
java -Dspring.profiles.active=prod -jar app.jar
方式3:操作系统环境变量
bash
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar
方式4:配置文件(application.yml)
yaml
spring:
profiles:
active: dev
方式5:Maven profile
xml
<profiles>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
打包时指定:
bash
mvn clean package -P prod
配置加载顺序:
- 加载
application.yml(主配置) - 根据激活的profile加载
application-{profile}.yml - profile配置覆盖主配置
- 命令行参数覆盖配置文件
同时激活多个profile:
bash
java -jar app.jar --spring.profiles.active=prod,redis-cluster
最佳实践:
- 开发环境:IDEA配置或本地
application.yml - 测试/生产:启动脚本通过命令行参数指定
- 避免在代码中硬编码profile
Q2: @ConfigurationProperties和@Value有什么区别?什么时候用哪个?
参考答案:
核心区别对比:
| 特性 | @ConfigurationProperties | @Value |
|---|---|---|
| 绑定方式 | 批量绑定(前缀匹配) | 单个绑定(精确key) |
| 松散绑定 | 支持(app-name/appName/app_name都行) | 不支持(必须精确匹配) |
| SpEL表达式 | 不支持 | 支持(#{}) |
| JSR-303校验 | 支持(@Validated) | 不支持 |
| 复杂类型 | 支持(List、Map、嵌套对象) | 不支持 |
| 元数据支持 | 支持(IDE智能提示) | 不支持 |
| 类型安全 | 编译期检查 | 运行时检查 |
| 默认值 | 字段赋值 | ${key:defaultValue} |
@ConfigurationProperties适用场景:
- 配置项较多(3个以上相关配置)
java
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private Integer port;
private Duration timeout;
private List<String> allowedOrigins;
// ... 10个配置
}
- 需要参数校验
java
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank
@Size(min = 3, max = 50)
private String name;
@Min(1024)
@Max(65535)
private Integer port;
}
- 复杂数据结构
java
@ConfigurationProperties(prefix = "third-party")
public class ThirdPartyProperties {
private PaymentConfig payment;
private List<SmsTemplate> smsTemplates;
private Map<String, ApiConfig> apis;
}
@Value适用场景:
- 单个简单配置
java
@Value("${app.name}")
private String appName;
- 需要SpEL表达式
java
@Value("#{systemProperties['user.home']}")
private String userHome;
@Value("#{10 * 1024 * 1024}") // 10MB
private Long maxFileSize;
- 动态计算
java
@Value("${app.env}")
private String env;
@Value("#{env == 'prod' ? 'https://api.prod.com' : 'http://localhost:8080'}")
private String apiUrl;
最佳实践:
- 配置类 :优先使用
@ConfigurationProperties - 单个配置 :使用
@Value - 需要SpEL :使用
@Value - 需要校验 :使用
@ConfigurationProperties + @Validated
Q3: SpEL表达式有哪些常用功能?如何在@Value中使用?
参考答案:
SpEL(Spring Expression Language)核心功能:
1. 字面量
java
@Value("#{'Hello World'}") // 字符串
private String str;
@Value("#{100}") // 数字
private Integer num;
@Value("#{true}") // 布尔
private Boolean bool;
2. 算术运算
java
@Value("#{10 + 20}") // 加法:30
private Integer sum;
@Value("#{100 - 30}") // 减法:70
private Integer diff;
@Value("#{5 * 6}") // 乘法:30
private Integer product;
@Value("#{100 / 4}") // 除法:25
private Integer quotient;
@Value("#{10 % 3}") // 取模:1
private Integer mod;
@Value("#{2 ^ 10}") // 幂运算:1024
private Integer power;
3. 关系运算
java
@Value("#{10 > 5}") // 大于:true
@Value("#{10 >= 10}") // 大于等于:true
@Value("#{5 < 3}") // 小于:false
@Value("#{10 == 10}") // 等于:true
@Value("#{10 != 5}") // 不等于:true
4. 逻辑运算
java
@Value("#{true and false}") // 与:false
@Value("#{true or false}") // 或:true
@Value("#{not true}") // 非:false
@Value("#{(10 > 5) and (20 < 30)}") // 组合:true
5. 三元运算符
java
@Value("${app.env}")
private String env;
@Value("#{env == 'prod' ? 80 : 8080}")
private Integer port;
// Elvis运算符(简化的三元)
@Value("#{env ?: 'dev'}") // env为null时使用'dev'
private String environment;
// 安全导航运算符
@Value("#{user?.name}") // user为null时不抛异常,返回null
private String userName;
6. 正则表达式
java
@Value("${user.email}")
private String email;
@Value("#{email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'}")
private Boolean isValidEmail;
7. 调用方法
java
// 调用String方法
@Value("#{userName.toUpperCase()}")
private String upperName;
@Value("#{userName.substring(0, 3)}")
private String prefix;
// 调用静态方法
@Value("#{T(java.lang.Math).random()}")
private Double random;
@Value("#{T(java.time.LocalDate).now()}")
private LocalDate today;
8. 引用Bean属性
java
// 引用名为systemConfig的Bean的属性
@Value("#{systemConfig.appName}")
private String appName;
@Value("#{systemConfig.version.toUpperCase()}")
private String version;
9. 集合操作
java
// 过滤集合
@Value("#{numbers.?[#this > 5]}") // 筛选大于5的元素
private List<Integer> filtered;
// 映射集合
@Value("#{users.![name]}") // 提取所有用户的name
private List<String> names;
// 访问元素
@Value("#{users[0].email}")
private String firstEmail;
// Map取值
@Value("#{configs['database.url']}")
private String dbUrl;
实战案例:
场景1:根据环境动态配置
java
@Value("${spring.profiles.active}")
private String profile;
@Value("#{profile == 'prod' ? 'https://api.prod.com' : 'http://localhost:8080'}")
private String apiUrl;
场景2:配置值计算
java
@Value("${thread.pool.core-size:10}")
private Integer coreSize;
@Value("#{coreSize * 2}") // 最大线程数 = 核心线程数 * 2
private Integer maxSize;
@Value("#{coreSize * 10}") // 队列容量 = 核心线程数 * 10
private Integer queueCapacity;
场景3:配置校验
java
@Value("${server.port}")
private Integer port;
@Value("#{port >= 1024 and port <= 65535 ? port : 8080}")
private Integer validatedPort;
注意事项:
- SpEL语法错误会导致启动失败
- 复杂逻辑建议用Java代码实现,保持可读性
- SpEL表达式不支持@ConfigurationProperties
Q4: 如何保护配置文件中的敏感信息?有哪些方案?
参考答案:
敏感信息包括:
- 数据库密码
- Redis密码
- 第三方API密钥(支付、短信等)
- 证书私钥
- Token/Secret
保护方案对比:
方案1:Jasypt加密(适合小型项目)
实现步骤:
- 引入依赖
xml
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
- 配置加密密钥
yaml
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD} # 从环境变量读取
algorithm: PBEWithMD5AndDES
- 加密敏感值
java
StringEncryptor encryptor = new DefaultPBEStringEncryptor();
String encrypted = encryptor.encrypt("MyPassword123!");
// 输出:oQw9K3xXzP7Yx8jF2nB4vA==
- 使用加密配置
yaml
spring:
datasource:
password: ENC(oQw9K3xXzP7Yx8jF2nB4vA==) # ENC()包裹
- 启动应用
bash
export JASYPT_ENCRYPTOR_PASSWORD=my-secret-key
java -jar app.jar
优点:
- ✅ 配置文件中无明文密码
- ✅ 对应用代码无侵入
- ✅ 简单易用
缺点:
- ❌ 加密密钥需要妥善保管
- ❌ 所有环境共用一个密钥
方案2:环境变量(推荐)
配置文件:
yaml
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:3306/${DB_NAME}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
启动脚本:
bash
#!/bin/bash
export DB_HOST=prod-db.company.com
export DB_USERNAME=app_user
export DB_PASSWORD=SecurePassword123!
java -jar app.jar
Docker Compose:
yaml
version: '3'
services:
app:
image: demo-service
environment:
- DB_HOST=prod-db
- DB_USERNAME=app_user
- DB_PASSWORD=SecurePassword123!
优点:
- ✅ 配置文件完全不含敏感信息
- ✅ 符合12-Factor App原则
- ✅ 容器化部署友好
- ✅ 每个环境独立配置
缺点:
- ❌ 需要在部署环境设置环境变量
方案3:配置中心(企业级推荐)
使用Nacos/Apollo/Spring Cloud Config:
yaml
# bootstrap.yml(只包含配置中心地址)
spring:
cloud:
nacos:
config:
server-addr: ${NACOS_SERVER}
username: ${NACOS_USERNAME}
password: ${NACOS_PASSWORD}
在Nacos Web界面配置:
yaml
spring.datasource.password=SecurePassword123! # Nacos内部加密
优点:
- ✅ 配置集中管理
- ✅ 支持配置加密
- ✅ 权限控制(不同角色看不同配置)
- ✅ 配置动态刷新
- ✅ 配置变更审计
- ✅ 配置版本管理
缺点:
- ❌ 需要部署配置中心
- ❌ 增加系统复杂度
方案4:密钥管理服务(最安全)
使用HashiCorp Vault/AWS KMS/Azure Key Vault:
java
@Configuration
public class SecretConfig {
@Bean
public DataSource dataSource() {
// 从密钥管理服务获取密码
String password = vaultClient.read("secret/database/password");
HikariDataSource ds = new HikariDataSource();
ds.setPassword(password); // 密码不在配置文件中
return ds;
}
}
优点:
- ✅ 最高安全级别
- ✅ 密钥轮换
- ✅ 访问审计
- ✅ 细粒度权限控制
缺点:
- ❌ 需要额外服务
- ❌ 学习成本高
- ❌ 增加依赖
最佳实践建议:
| 项目规模 | 推荐方案 | 理由 |
|---|---|---|
| 小型项目 | 环境变量 | 简单、容器化友好 |
| 中型项目 | Jasypt + 环境变量 | 加密配置 + 灵活部署 |
| 大型项目 | 配置中心(Nacos/Apollo) | 集中管理、权限控制 |
| 金融/安全敏感 | 密钥管理服务(Vault/KMS) | 最高安全级别 |
安全检查清单:
- 敏感配置不提交到Git(
.gitignore) - 使用配置模板文件(
.template.yml) - 生产环境使用环境变量或配置中心
- 启用配置加密
- 配置访问权限控制
- 定期轮换密钥
- 启用审计日志
Q5: Spring Boot配置的加载顺序是什么?如何解决配置冲突?
参考答案:
Spring Boot配置加载优先级(从高到低):
1. 命令行参数
java -jar app.jar --server.port=9090
2. Java系统属性(-D参数)
java -Dserver.port=9090 -jar app.jar
3. 操作系统环境变量
export SERVER_PORT=9090
4. application-{profile}.yml(激活的profile配置)
例如:application-prod.yml
5. application.yml(主配置文件)
6. @PropertySource加载的配置文件
7. 默认值(@Value注解的defaultValue)
@Value("${server.port:8080}")
配置文件位置优先级(从高到低):
1. file:./config/ (当前目录/config子目录)
2. file:./ (当前目录)
3. classpath:/config/ (classpath下config目录)
4. classpath:/ (classpath根目录)
实战示例:
场景: 同一个配置在多处定义
yaml
# application.yml(优先级:5)
server:
port: 8080
# application-prod.yml(优先级:4)
server:
port: 80
# 命令行参数(优先级:1)
java -jar app.jar --server.port=9090 --spring.profiles.active=prod
最终生效: server.port=9090(命令行参数优先级最高)
配置覆盖规则:
规则1:高优先级覆盖低优先级
yaml
# application.yml
server:
port: 8080
tomcat:
max-threads: 200
# application-prod.yml
server:
port: 80 # 覆盖
最终生效:
yaml
server:
port: 80 # 来自application-prod.yml
tomcat:
max-threads: 200 # 来自application.yml(未被覆盖)
规则2:属性级别覆盖(非对象级别)
yaml
# application.yml
server:
tomcat:
max-threads: 200
accept-count: 100
min-spare-threads: 10
# application-prod.yml
server:
tomcat:
max-threads: 500 # 只覆盖max-threads
最终生效:
yaml
server:
tomcat:
max-threads: 500 # 来自prod(覆盖)
accept-count: 100 # 来自application.yml(保留)
min-spare-threads: 10 # 来自application.yml(保留)
解决配置冲突的方法:
方法1:查看实际生效的配置
bash
# 启动时查看配置
java -jar app.jar --debug
# 或通过Actuator端点查看
http://localhost:8080/actuator/env
http://localhost:8080/actuator/configprops
方法2:使用Spring Boot Actuator
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
yaml
management:
endpoints:
web:
exposure:
include: env,configprops
访问:http://localhost:8080/actuator/env/server.port
输出:
json
{
"property": {
"source": "commandLineArgs",
"value": "9090"
},
"propertySources": [
{
"name": "commandLineArgs",
"property": {
"value": "9090",
"origin": "..."
}
},
{
"name": "application-prod.yml",
"property": {
"value": "80"
}
},
{
"name": "application.yml",
"property": {
"value": "8080"
}
}
]
}
方法3:显式声明配置来源
java
@Component
public class ConfigDebugger implements CommandLineRunner {
@Autowired
private Environment env;
@Override
public void run(String... args) {
// 打印配置来源
String port = env.getProperty("server.port");
System.out.println("server.port = " + port);
// 获取配置源
MutablePropertySources sources =
((ConfigurableEnvironment) env).getPropertySources();
for (PropertySource<?> source : sources) {
System.out.println("配置源:" + source.getName());
}
}
}
最佳实践:
- 开发环境:使用默认配置(application.yml)
- 测试/生产:通过profile或命令行参数覆盖
- 敏感信息:使用环境变量(最高优先级,最安全)
- 调试冲突:使用Actuator端点查看实际生效值
下一篇预告: 《自定义Starter开发实战》- 从零开发企业级Starter,掌握AutoConfiguration自动装配机制。