跳到主要内容

配置管理

Spring Boot 提供了强大的外部化配置支持,允许开发者将配置与代码分离,在不同环境中使用相同的应用代码。本章将深入讲解 Spring Boot 的配置管理机制。

外部化配置概述

什么是外部化配置?

外部化配置是将应用程序的配置信息从代码中分离出来,存储在外部文件或环境变量中。这样做的好处:

  • 环境隔离:同一份代码可以在开发、测试、生产环境运行
  • 配置热更新:无需重新编译即可修改配置
  • 安全性:敏感信息可以不包含在代码库中
  • 灵活性:支持多种配置来源和格式

配置加载顺序

Spring Boot 按照特定的顺序加载配置,后面的配置会覆盖前面的同名配置:

优先级从低到高:

1. 默认属性(SpringApplication.setDefaultProperties)
2. @PropertySource 注解
3. 应用配置文件(application.properties/yml)
├── JAR 包内的配置文件
├── JAR 包内的 Profile 配置文件
├── JAR 包外的配置文件
└── JAR 包外的 Profile 配置文件
4. 操作系统环境变量
5. Java 系统属性(System.getProperties())
6. JNDI 属性
7. ServletContext 初始化参数
8. ServletConfig 初始化参数
9. SPRING_APPLICATION_JSON 属性
10. 命令行参数
11. 测试属性(@SpringBootTest)
12. DevTools 全局设置

理解优先级

# 命令行参数优先级最高
java -jar app.jar --server.port=9090

# 环境变量次之
SERVER_PORT=8081 java -jar app.jar

# 配置文件最低
# application.yml 中 server.port=8080

配置文件

配置文件位置

Spring Boot 会自动从以下位置加载配置文件(按优先级从高到低):

1. 当前目录的 /config 子目录
./config/application.yml

2. 当前目录
./application.yml

3. classpath 的 /config 包
classpath:/config/application.yml

4. classpath 根目录
classpath:/application.yml

配置文件格式

Spring Boot 支持两种配置文件格式:

properties 格式

# application.properties
server.port=8080
server.servlet.context-path=/api

spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=root

logging.level.root=INFO
logging.level.com.example=DEBUG

YAML 格式(推荐)

# application.yml
server:
port: 8080
servlet:
context-path: /api

spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: root

logging:
level:
root: INFO
com.example: DEBUG

YAML 格式优势

特性说明
层次结构使用缩进表示层级关系,更直观
减少重复避免重复的前缀
支持复杂结构支持列表、Map 等复杂类型
可读性配置更易读、易维护
注意

如果同时存在 application.propertiesapplication.yml.properties 文件优先级更高。建议整个项目统一使用一种格式。

多文档配置

YAML 支持在一个文件中定义多个文档,使用 --- 分隔:

# 通用配置
server:
port: 8080

---
# 开发环境配置
spring:
config:
activate:
on-profile: dev

server:
port: 8081

---
# 生产环境配置
spring:
config:
activate:
on-profile: prod

server:
port: 80

Profile 环境配置

什么是 Profile?

Profile 是 Spring 提供的一种机制,用于根据不同的环境加载不同的配置。

创建 Profile 配置文件

按照 application-{profile}.yml 命名规则创建:

resources/
├── application.yml # 主配置
├── application-dev.yml # 开发环境
├── application-test.yml # 测试环境
└── application-prod.yml # 生产环境

各环境配置示例

# application.yml(通用配置)
spring:
application:
name: myapp

myapp:
name: 默认应用
# application-dev.yml
server:
port: 8080

spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb_dev
username: root
password: root

logging:
level:
root: DEBUG

myapp:
name: 开发环境应用
# application-prod.yml
server:
port: 80

spring:
datasource:
url: jdbc:mysql://prod-db:3306/mydb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}

logging:
level:
root: WARN

myapp:
name: 生产环境应用

激活 Profile

方式一:配置文件

# application.yml
spring:
profiles:
active: dev

方式二:命令行参数

java -jar app.jar --spring.profiles.active=prod

方式三:环境变量

# Linux/macOS
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar

# Windows
set SPRING_PROFILES_ACTIVE=prod
java -jar app.jar

方式四:IDE 配置

在 IntelliJ IDEA 中:

  1. 打开 Run/Debug Configurations
  2. 在 Environment variables 中添加 SPRING_PROFILES_ACTIVE=dev
  3. 或在 VM options 中添加 -Dspring.profiles.active=dev

激活多个 Profile

spring:
profiles:
active: dev,mysql,redis
java -jar app.jar --spring.profiles.active=prod,mysql,redis

Profile 分组

Spring Boot 2.4+ 支持Profile 分组:

spring:
profiles:
group:
dev:
- dev-db
- dev-mq
prod:
- prod-db
- prod-mq
active: dev

@Profile 条件装配

使用 @Profile 注解控制 Bean 的加载:

@Configuration
public class DataSourceConfig {

@Bean
@Profile("dev")
public DataSource devDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/mydb_dev")
.username("root")
.password("root")
.build();
}

@Bean
@Profile("prod")
public DataSource prodDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://prod-db:3306/mydb")
.username("${DB_USERNAME}")
.password("${DB_PASSWORD}")
.build();
}
}

在 Service 层使用

@Service
@Profile("dev")
public class DevEmailService implements EmailService {
// 开发环境的邮件服务(打印日志)
}

@Service
@Profile("prod")
public class ProdEmailService implements EmailService {
// 生产环境的邮件服务(真实发送)
}

配置属性绑定

@Value 注解

最简单的方式是使用 @Value 注解直接注入属性:

@Service
public class MyService {

@Value("${myapp.name}")
private String appName;

@Value("${myapp.version:1.0.0}")
private String version; // 默认值 1.0.0

@Value("${myapp.features}")
private List<String> features; // 支持列表类型

public void printInfo() {
System.out.println("应用名称: " + appName);
System.out.println("版本: " + version);
}
}

配置文件

myapp:
name: 我的应用
version: 2.0.0
features:
- 功能1
- 功能2
- 功能3

@ConfigurationProperties 注解

对于复杂的配置,推荐使用 @ConfigurationProperties

@Data
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {

private String name;
private String version;
private Boolean enabled = true;
private List<String> features = new ArrayList<>();
private Database database = new Database();
private Map<String, String> metadata = new HashMap<>();

@Data
public static class Database {
private String url;
private String username;
private String password;
private Integer poolSize = 10;
}
}

对应配置

myapp:
name: 我的应用
version: 2.0.0
enabled: true
features:
- 功能1
- 功能2
database:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: root
pool-size: 20
metadata:
author: 张三
department: 技术部

使用配置类

@Service
@RequiredArgsConstructor
public class MyService {

private final MyAppProperties properties;

public void printInfo() {
System.out.println("应用名称: " + properties.getName());
System.out.println("数据库URL: " + properties.getDatabase().getUrl());
}
}

启用配置属性扫描

方式一:使用 @EnableConfigurationProperties

@Configuration
@EnableConfigurationProperties(MyAppProperties.class)
public class AppConfig {
}

方式二:在配置类上使用 @Component

@Data
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {
// ...
}

方式三:使用 @ConfigurationPropertiesScan

@SpringBootApplication
@ConfigurationPropertiesScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

宽松绑定

Spring Boot 支持多种属性命名格式:

配置文件格式说明
myapp.database.url标准小写点分隔
myapp.database-url短横线分隔
myapp.databaseUrl驼峰命名
MYAPP_DATABASE_URL环境变量大写下划线

示例

# 以下写法都能绑定到 database.url 属性
myapp:
database:
url: jdbc:mysql://localhost:3306/mydb
# 或者
database-url: jdbc:mysql://localhost:3306/mydb
# 环境变量方式
MYAPP_DATABASE_URL=jdbc:mysql://localhost:3306/mydb

配置验证

使用 JSR-303 注解验证配置:

@Data
@ConfigurationProperties(prefix = "myapp")
@Validated
public class MyAppProperties {

@NotBlank(message = "应用名称不能为空")
private String name;

@Pattern(regexp = "^\\d+\\.\\d+\\.\\d+$", message = "版本号格式不正确")
private String version;

@Min(value = 1, message = "端口不能小于1")
@Max(value = 65535, message = "端口不能大于65535")
private Integer port;

@Valid
private Database database = new Database();

@Data
public static class Database {
@NotBlank(message = "数据库URL不能为空")
private String url;

@NotBlank(message = "数据库用户名不能为空")
private String username;
}
}

添加验证依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

高级配置

配置属性占位符

在配置文件中引用其他属性:

app:
name: MyApp
version: 1.0.0
description: ${app.name} 是一个优秀的应用
author: ${AUTHOR:Unknown} # 使用环境变量,默认值 Unknown

随机值配置

Spring Boot 提供随机值生成器:

app:
secret: ${random.value} # 随机字符串
number: ${random.int} # 随机整数
big-number: ${random.long} # 随机长整数
uuid: ${random.uuid} # 随机UUID
port: ${random.int[10000,20000]} # 指定范围的随机整数

导入外部配置

使用 spring.config.import 导入其他配置文件:

# application.yml
spring:
config:
import:
- optional:file:./dev.properties # 可选导入
- classpath:additional-config.yml # 类路径导入

配置加密

对于敏感信息,可以使用 Jasypt 进行加密:

添加依赖

<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>

配置加密属性

# 加密后的密码
spring:
datasource:
password: ENC(加密后的字符串)

# 加密密钥(建议通过环境变量传入)
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD}

生成加密字符串

@Test
void encryptPassword() {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("my-secret-key");
String encrypted = encryptor.encrypt("my-password");
System.out.println("加密后: ENC(" + encrypted + ")");
}

配置最佳实践

1. 合理分层配置

application.yml          # 通用配置
├── 应用名称、版本
├── 公共配置
└── 默认值

application-dev.yml # 开发环境
├── 本地数据库配置
├── 调试开关
└── 详细日志

application-prod.yml # 生产环境
├── 生产数据库配置
├── 环境变量引用
└── 性能优化配置

2. 敏感信息处理

# 不推荐:硬编码密码
spring:
datasource:
password: root123

# 推荐:使用环境变量
spring:
datasource:
password: ${DB_PASSWORD}

# 推荐:使用配置中心
spring:
datasource:
password: ${vault.database.password}

3. 配置文档化

@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {

/**
* 应用名称,用于日志和监控标识
*/
private String name;

/**
* 数据库连接池大小,默认10
*/
private Integer poolSize = 10;
}

4. 配置变更通知

@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties implements ConfigurationPropertiesRebindable {

@Bean
public ConfigurationPropertiesRebinder rebinder() {
return new ConfigurationPropertiesRebinder();
}
}

5. 配置值校验

@PostConstruct
public void validate() {
if (port < 1024 || port > 65535) {
throw new IllegalArgumentException("端口号必须在 1024-65535 之间");
}
}

配置文件示例

完整配置示例

# application.yml
spring:
application:
name: myapp

profiles:
active: dev

# 数据源配置
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:mydb}
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:root}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000

# JPA 配置
jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQLDialect

# 缓存配置
cache:
type: redis
redis:
time-to-live: 600000

# Redis 配置
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: 0

# 服务器配置
server:
port: ${SERVER_PORT:8080}
servlet:
context-path: /api
error:
include-message: always
include-binding-errors: always

# 日志配置
logging:
level:
root: INFO
com.example: DEBUG
file:
name: logs/myapp.log
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# Actuator 配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,env
endpoint:
health:
show-details: when-authorized

# 自定义配置
myapp:
name: MyApp
version: 1.0.0
enabled: true
features:
- feature1
- feature2
security:
jwt:
secret: ${JWT_SECRET:default-secret-key}
expiration: 86400000

虚拟线程配置

什么是虚拟线程?

虚拟线程(Virtual Threads)是 Java 21 引入的轻量级线程,由 JVM 而非操作系统管理。与传统线程(平台线程)相比,虚拟线程有以下优势:

特性平台线程虚拟线程
创建成本高(需要操作系统资源)低(由 JVM 管理)
内存占用约 1MB 栈空间约 1KB 栈空间
数量限制受操作系统限制(通常几千)几乎无限制(百万级)
适用场景CPU 密集型任务I/O 密集型任务
阻塞影响阻塞时占用系统资源阻塞时几乎不占资源

工作原理:虚拟线程在执行 I/O 操作(如网络请求、数据库查询)时会自动让出底层平台线程,等 I/O 完成后再恢复执行。这使得少量平台线程可以支撑大量虚拟线程的并发执行。

启用虚拟线程

Spring Boot 3.2+ 支持虚拟线程,需要 Java 21+:

# application.yml
spring:
threads:
virtual:
enabled: true

或者使用 properties 格式

spring.threads.virtual.enabled=true

虚拟线程的应用场景

启用虚拟线程后,以下场景会自动使用虚拟线程:

1. Web 服务器请求处理

Tomcat 和 Jetty 会使用虚拟线程处理每个 HTTP 请求:

@RestController
public class UserController {

// 每个 HTTP 请求在独立的虚拟线程中处理
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// 阻塞的数据库调用不再占用平台线程
return userService.findById(id);
}
}

2. 异步任务执行

@Async 方法会在虚拟线程中执行:

@Service
public class NotificationService {

@Async
public void sendEmail(String to, String content) {
// 在虚拟线程中异步发送邮件
emailClient.send(to, content);
}
}

3. 定时任务

定时任务会在虚拟线程中执行:

@Service
public class ScheduledTasks {

@Scheduled(fixedRate = 5000)
public void performTask() {
// 在虚拟线程中执行定时任务
dataProcessor.process();
}
}

4. 异步请求处理

@RestController
public class AsyncController {

@GetMapping("/async")
public CompletableFuture<String> asyncRequest() {
return CompletableFuture.supplyAsync(() -> {
// 在虚拟线程中执行
return heavyOperation();
});
}
}

虚拟线程与传统线程对比示例

传统方式(线程池)

@Service
public class TraditionalService {

private final ExecutorService executor = Executors.newFixedThreadPool(10);

public List<User> fetchAllUsers(List<Long> ids) {
List<Future<User>> futures = new ArrayList<>();

for (Long id : ids) {
futures.add(executor.submit(() -> userRepository.findById(id)));
}

// 等待所有任务完成
List<User> users = new ArrayList<>();
for (Future<User> future : futures) {
users.add(future.get());
}
return users;
}
}

虚拟线程方式

@Service
public class VirtualThreadService {

// 不需要手动管理线程池
public List<User> fetchAllUsers(List<Long> ids) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<User>> futures = ids.stream()
.map(id -> executor.submit(() -> userRepository.findById(id)))
.toList();

List<User> users = new ArrayList<>();
for (Future<User> future : futures) {
users.add(future.get());
}
return users;
}
}
}

虚拟线程的注意事项

1. 不适合 CPU 密集型任务

// 不推荐:CPU 密集型任务使用虚拟线程
@Async
public void cpuIntensiveTask() {
// 大量计算会阻塞虚拟线程的载体线程
BigInteger result = factorial(100000);
}

// 推荐:CPU 密集型任务仍使用平台线程
@Async
public void cpuIntensiveTask() {
// 使用专门的线程池处理 CPU 密集型任务
ForkJoinPool.commonPool().execute(() -> {
BigInteger result = factorial(100000);
});
}

2. 避免使用 synchronized 锁

synchronized 会固定虚拟线程到载体线程,失去虚拟线程的优势:

// 不推荐
public synchronized void process() {
// synchronized 会固定虚拟线程
blockingOperation();
}

// 推荐:使用 ReentrantLock
private final ReentrantLock lock = new ReentrantLock();

public void process() {
lock.lock();
try {
blockingOperation();
} finally {
lock.unlock();
}
}

3. 数据库连接池配置

使用虚拟线程时,可能需要调整连接池配置:

spring:
threads:
virtual:
enabled: true
datasource:
hikari:
maximum-pool-size: 50 # 虚拟线程数量多,需要更多连接
minimum-idle: 10

4. 线程本地变量(ThreadLocal)注意

虚拟线程数量可能非常多,ThreadLocal 可能占用大量内存:

// 谨慎使用 ThreadLocal
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 推荐:使用局部变量或依赖注入

虚拟线程最佳实践

# application.yml
spring:
threads:
virtual:
enabled: true

# 针对 I/O 密集型应用优化数据源配置
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10

# 异步任务配置
task:
execution:
pool:
core-size: 0 # 虚拟线程模式下忽略
max-size: 0 # 虚拟线程模式下忽略
@Configuration
public class VirtualThreadConfig {

// 自定义虚拟线程执行器(可选)
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
}

如何判断是否应该使用虚拟线程?

适合使用虚拟线程的场景

  • 高并发 Web 服务
  • 大量 I/O 操作(数据库、HTTP 调用、文件读写)
  • 需要简化并发代码
  • 传统线程池成为瓶颈

不适合使用虚拟线程的场景

  • CPU 密集型计算
  • 使用了大量 synchronized 的代码
  • ThreadLocal 使用过多
  • 需要精细控制线程资源

小结

本章我们学习了:

  1. 外部化配置:理解配置加载顺序和优先级
  2. 配置文件:properties 和 YAML 格式的使用
  3. Profile:多环境配置管理
  4. 属性绑定:@Value 和 @ConfigurationProperties
  5. 高级特性:占位符、随机值、导入配置、加密
  6. 虚拟线程:Spring Boot 3.2+ 的虚拟线程支持
  7. 最佳实践:配置分层、敏感信息处理、文档化

练习

  1. 创建一个包含 dev、test、prod 三个环境的配置文件
  2. 使用 @ConfigurationProperties 绑定复杂配置
  3. 使用环境变量覆盖配置文件中的属性
  4. 为配置属性添加验证注解
  5. 实现配置的加密存储

参考资源