📌 系列说明 :本文是《Spring Boot从入门到底层原理》20篇系列的第三篇。
- 前置知识:已完成前两篇《Spring Boot 101》和《Starter深度拆解》
- 核心目标:彻底掌握Spring Boot 4.x配置文件的所有用法,包括多环境切换、配置加密等
- 版本基准 :Spring Boot 4.0.x + JDK 21+
- 预计耗时:阅读40分钟 + 实操90分钟
一、为什么配置文件如此重要?
1.1 从痛点说起:硬编码的灾难
在没有配置文件的时代,应用参数是这样写的:
java
// ❌ 硬编码方式(绝对不要这样做!)
public class DatabaseConfig {
private String url = "jdbc:mysql://localhost:3306/mydb";
private String username = "root";
private String password = "123456";
private int port = 8080;
}
问题:
- 🔴 环境切换困难:开发、测试、生产环境需要修改代码重新编译
- 🔴 安全风险:数据库密码等敏感信息直接暴露在代码中
- 🔴 维护成本高:每次修改配置都需要重新部署
1.2 Spring Boot 4.x的解决方案:外部化配置
Spring Boot 4.x在3.x的基础上进一步优化了配置体验:
java
// ✅ Spring Boot 4.x 配置分离方式
@RestController
public class ConfigController {
@Value("${app.name}")
private String appName;
@Autowired
private AppProperties appProperties;
}
Spring Boot 4.x 配置新特性:
- ✅ 模块化自动配置:47个轻量模块,按需加载
- ✅ JSpecify空安全集成:编译期空指针检查
- ✅ 增强的Profile支持:更灵活的多环境管理
- ✅ 配置绑定性能提升:AOT优化,启动更快
二、配置文件基础:properties vs yml
2.1 两种配置文件格式对比
Spring Boot 4.x继续支持两种配置文件格式:
| 特性 | application.properties | application.yml |
|---|---|---|
| 语法 | Key-Value键值对 | YAML层级结构 |
| 可读性 | 一般 | 优秀(层级清晰) |
| 空格敏感 | 否 | 是(缩进必须一致) |
| 注释符号 | # |
# |
| 官方推荐 | 支持 | 推荐 |
| Spring Boot 4.x增强 | - | 支持YAML锚点引用 |
2.2 实战:创建完整的多模块配置项目
步骤1:创建Maven父项目
在IDEA中:
vbnet
File → New → Project → Maven → Next
填写项目信息:
| 字段 | 值 |
|---|---|
| Name | spring-boot-config-demo |
| Location | 选择你的工作目录 |
| GroupId | com.example |
| ArtifactId | spring-boot-config-demo |
| Version | 1.0.0 |
| Packaging | pom |
步骤2:配置父项目 pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-boot-config-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>Spring Boot Config Demo Parent</name>
<description>Spring Boot 4.x配置文件演示项目(父项目)</description>
<modules>
<module>config-app</module>
</modules>
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>4.0.0</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
⚠️ Spring Boot 4.x 关键变化:
- Java版本:最低要求 Java 17,推荐 Java 21+
- Maven插件版本:spring-boot-maven-plugin 需与Spring Boot版本一致
- 编译器配置 :建议添加
-parameters参数支持方法名反射
步骤3:创建子模块
vbnet
右键父项目 → New → Module → Maven → Next
填写模块信息:
| 字段 | 值 |
|---|---|
| Name | config-app |
| GroupId | com.example |
| ArtifactId | config-app |
| Version | 1.0.0 |
| Packaging | jar |
步骤4:配置子模块 pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-config-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>config-app</artifactId>
<packaging>jar</packaging>
<name>Config App</name>
<description>Spring Boot 4.x配置文件演示应用</description>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Actuator(查看配置信息) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Boot Configuration Processor(配置元数据) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSpecify空安全注解(Spring Boot 4.x新特性) -->
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
💡 Spring Boot 4.x 新增依赖:
jspecify:空安全注解,编译期检查空指针
2.3 application.properties 完整示例
路径 :config-app/src/main/resources/application.properties
properties
# ===========================================
# 应用基础配置
# ===========================================
spring.application.name=config-app
# ===========================================
# 服务器配置
# ===========================================
server.port=8080
server.servlet.context-path=/api
# ===========================================
# 数据库配置
# ===========================================
spring.datasource.url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# ===========================================
# 自定义配置
# ===========================================
app.name=我的应用
app.version=1.0.0
app.description=Spring Boot 4.x配置文件演示应用
app.owner=开发团队
app.contact.email=support@example.com
# ===========================================
# 日志配置
# ===========================================
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.file.name=logs/app.log
logging.file.max-size=10MB
logging.file.max-history=30
# ===========================================
# Actuator 监控配置
# ===========================================
management.endpoints.web.exposure.include=health,info,configprops,env
management.endpoint.health.show-details=always
2.4 application.yml 完整示例
路径 :config-app/src/main/resources/application.yml
yaml
# ===========================================
# 应用基础配置
# ===========================================
spring:
application:
name: config-app
# ===========================================
# 服务器配置
# ===========================================
server:
port: 8080
servlet:
context-path: /api
# ===========================================
# 数据库配置
# ===========================================
datasource:
url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# ===========================================
# 自定义配置
# ===========================================
app:
name: 我的应用
version: 1.0.0
description: Spring Boot 4.x配置文件演示应用
owner: 开发团队
contact:
email: support@example.com
phone: 400-888-8888
# ===========================================
# 日志配置
# ===========================================
logging:
level:
root: INFO
com.example: DEBUG
file:
name: logs/app.log
max-size: 10MB
max-history: 30
# ===========================================
# Actuator 监控配置
# ===========================================
management:
endpoints:
web:
exposure:
include: health,info,configprops,env
endpoint:
health:
show-details: always
2.5 Spring Boot 4.x YAML新特性:锚点引用
Spring Boot 4.x 增强了对YAML锚点的支持,可以复用配置:
yaml
# ===========================================
# 使用YAML锚点复用配置
# ===========================================
defaults: &defaults
timeout: 30000
retry: 3
enabled: true
app:
service-a:
<<: *defaults
name: 服务A
timeout: 60000 # 可覆盖默认值
service-b:
<<: *defaults
name: 服务B
# 继承所有defaults配置
三、配置文件读取的三种方式
3.1 方式一:@Value 注解(简单值注入)
适用场景:读取单个配置项
步骤1:创建测试Controller
路径:config-app/src/main/java/com/example/config/controller/ConfigController.java
java
package com.example.config.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 配置文件读取演示 - @Value方式
* Spring Boot 4.x 支持更灵活的SpEL表达式
*/
@RestController
public class ConfigController {
/**
* 读取简单配置项
* 语法:${配置键}
*/
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
@Value("${app.description:默认描述}")
private String appDescription;
@Value("${app.unknown:默认值}")
private String unknownConfig;
@Value("${server.port}")
private Integer serverPort;
/**
* 读取列表配置
*/
@Value("${app.features:}")
private String[] features;
/**
* 测试@Value读取配置
* GET /api/config/value
*/
@GetMapping("/config/value")
public Map<String, Object> getConfigByValue() {
Map<String, Object> result = new HashMap<>();
result.put("appName", appName);
result.put("appVersion", appVersion);
result.put("appDescription", appDescription);
result.put("unknownConfig", unknownConfig);
result.put("serverPort", serverPort);
result.put("features", features);
return result;
}
/**
* 读取SpEL表达式
* Spring Boot 4.x 增强SpEL支持
*/
@Value("#{T(java.lang.Math).random() * 100}")
private Double randomValue;
@Value("#{systemProperties['user.name']}")
private String systemUser;
@GetMapping("/config/random")
public Map<String, Object> getRandomValue() {
Map<String, Object> result = new HashMap<>();
result.put("randomValue", randomValue);
result.put("systemUser", systemUser);
return result;
}
}
步骤2:添加列表配置到application.yml
yaml
app:
features:
- 用户管理
- 订单管理
- 支付管理
- 报表统计
步骤3:测试接口
bash
# 测试@Value读取
curl http://localhost:8080/api/config/value
# 测试随机值
curl http://localhost:8080/api/config/random
预期响应:
json
{
"appName": "我的应用",
"appVersion": "1.0.0",
"appDescription": "Spring Boot 4.x配置文件演示应用",
"unknownConfig": "默认值",
"serverPort": 8080,
"features": ["用户管理", "订单管理", "支付管理", "报表统计"]
}
3.2 方式二:@ConfigurationProperties(类型安全配置)
适用场景 :读取一组相关配置,Spring Boot 4.x 推荐方式
步骤1:创建配置属性类
路径:config-app/src/main/java/com/example/config/properties/AppProperties.java
java
package com.example.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 应用配置属性类
*
* Spring Boot 4.x 增强:
* 1. 更好的IDE提示支持
* 2. 编译期空安全检查(配合JSpecify)
* 3. 配置绑定性能提升(AOT优化)
*/
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
/**
* 应用名称
*/
private String name;
/**
* 应用版本
*/
private String version;
/**
* 应用描述
*/
private String description;
/**
* 应用所有者
*/
private String owner;
/**
* 联系信息
*/
private Contact contact = new Contact();
/**
* 功能列表
*/
private List<String> features = new ArrayList<>();
/**
* 数据库配置
*/
private Database database = new Database();
// ========== 内部类:联系信息 ==========
public static class Contact {
private String email;
private String phone;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
// ========== 内部类:数据库配置 ==========
public static class Database {
private String host;
private Integer port;
private String name;
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public Integer getPort() { return port; }
public void setPort(Integer port) { this.port = port; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
// ========== Getter 和 Setter ==========
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getOwner() { return owner; }
public void setOwner(String owner) { this.owner = owner; }
public Contact getContact() { return contact; }
public void setContact(Contact contact) { this.contact = contact; }
public List<String> getFeatures() { return features; }
public void setFeatures(List<String> features) { this.features = features; }
public Database getDatabase() { return database; }
public void setDatabase(Database database) { this.database = database; }
@Override
public String toString() {
return "AppProperties{" +
"name='" + name + '\'' +
", version='" + version + '\'' +
", description='" + description + '\'' +
", owner='" + owner + '\'' +
", contact=" + contact +
", features=" + features +
", database=" + database +
'}';
}
}
步骤2:添加数据库配置到application.yml
yaml
app:
database:
host: localhost
port: 3306
name: demo_db
步骤3:创建使用配置属性的Controller
路径:config-app/src/main/java/com/example/config/controller/AppPropertiesController.java
java
package com.example.config.controller;
import com.example.config.properties.AppProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 使用@ConfigurationProperties读取配置
*/
@RestController
public class AppPropertiesController {
@Autowired
private AppProperties appProperties;
/**
* 获取所有应用配置
* GET /api/config/properties
*/
@GetMapping("/config/properties")
public Map<String, Object> getAppProperties() {
Map<String, Object> result = new HashMap<>();
result.put("name", appProperties.getName());
result.put("version", appProperties.getVersion());
result.put("description", appProperties.getDescription());
result.put("owner", appProperties.getOwner());
result.put("contact", appProperties.getContact());
result.put("features", appProperties.getFeatures());
result.put("database", appProperties.getDatabase());
return result;
}
/**
* 获取联系信息
* GET /api/config/contact
*/
@GetMapping("/config/contact")
public Map<String, String> getContact() {
Map<String, String> result = new HashMap<>();
result.put("email", appProperties.getContact().getEmail());
result.put("phone", appProperties.getContact().getPhone());
return result;
}
/**
* 获取数据库配置
* GET /api/config/database
*/
@GetMapping("/config/database")
public Map<String, Object> getDatabase() {
Map<String, Object> result = new HashMap<>();
result.put("host", appProperties.getDatabase().getHost());
result.put("port", appProperties.getDatabase().getPort());
result.put("name", appProperties.getDatabase().getName());
return result;
}
}
步骤4:启用配置属性绑定(Spring Boot 4.x)
路径:config-app/src/main/java/com/example/config/ConfigApplication.java
java
package com.example.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import com.example.config.properties.AppProperties;
/**
* 应用启动类
*
* Spring Boot 4.x 变化:
* 1. 配置类如有@Component,可省略@EnableConfigurationProperties
* 2. 但显式声明更清晰,推荐保留
*/
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
步骤5:测试接口
bash
# 获取所有配置
curl http://localhost:8080/api/config/properties
# 获取联系信息
curl http://localhost:8080/api/config/contact
# 获取数据库配置
curl http://localhost:8080/api/config/database
预期响应:
json
{
"name": "我的应用",
"version": "1.0.0",
"description": "Spring Boot 4.x配置文件演示应用",
"owner": "开发团队",
"contact": {
"email": "support@example.com",
"phone": "400-888-8888"
},
"features": ["用户管理", "订单管理", "支付管理", "报表统计"],
"database": {
"host": "localhost",
"port": 3306,
"name": "demo_db"
}
}
3.3 方式三:Environment 接口(编程式读取)
适用场景:动态读取配置、需要更灵活的场景
步骤1:创建Environment测试Controller
路径:config-app/src/main/java/com/example/config/controller/EnvironmentController.java
java
package com.example.config.controller;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
/**
* 使用Environment接口读取配置
*/
@RestController
public class EnvironmentController {
@Autowired
private Environment environment;
/**
* 通过Environment读取配置
* GET /api/config/env?key=app.name
*/
@GetMapping("/config/env")
public Map<String, Object> getConfigByKey(@RequestParam String key) {
Map<String, Object> result = new HashMap<>();
result.put("key", key);
result.put("value", environment.getProperty(key));
result.put("exists", environment.containsProperty(key));
return result;
}
/**
* 读取配置(带默认值)
* GET /api/config/env-with-default?key=app.unknown&default=默认值
*/
@GetMapping("/config/env-with-default")
public Map<String, Object> getConfigWithDefault(
@RequestParam String key,
@RequestParam(defaultValue = "默认值") String defaultVal) {
Map<String, Object> result = new HashMap<>();
result.put("key", key);
result.put("value", environment.getProperty(key, defaultVal));
return result;
}
/**
* 获取所有属性源
* GET /api/config/property-sources
*/
@GetMapping("/config/property-sources")
public Map<String, Object> getPropertySources() {
Map<String, Object> result = new HashMap<>();
String[] sources = Arrays.stream(environment.getPropertySources())
.map(source -> source.getName())
.toArray(String[]::new);
result.put("propertySources", sources);
result.put("count", sources.length);
return result;
}
/**
* 获取活跃的环境Profile
* GET /api/config/active-profiles
*/
@GetMapping("/config/active-profiles")
public Map<String, Object> getActiveProfiles() {
Map<String, Object> result = new HashMap<>();
result.put("activeProfiles", Arrays.asList(environment.getActiveProfiles()));
result.put("defaultProfiles", Arrays.asList(environment.getDefaultProfiles()));
return result;
}
}
步骤2:测试接口
bash
# 读取指定配置
curl "http://localhost:8080/api/config/env?key=app.name"
# 读取配置(带默认值)
curl "http://localhost:8080/api/config/env-with-default?key=app.unknown&default=默认值"
# 获取所有属性源
curl http://localhost:8080/api/config/property-sources
# 获取活跃Profile
curl http://localhost:8080/api/config/active-profiles
3.4 三种方式对比总结
| 特性 | @Value | @ConfigurationProperties | Environment |
|---|---|---|---|
| 类型安全 | ❌ 否 | ✅ 是 | ❌ 否 |
| IDE提示 | ❌ 有限 | ✅ 完整 | ❌ 无 |
| 松散绑定 | ❌ 不支持 | ✅ 支持 | ❌ 不支持 |
| 复杂对象 | ❌ 困难 | ✅ 优秀 | ❌ 困难 |
| JSR380校验 | ❌ 不支持 | ✅ 支持 | ❌ 不支持 |
| Spring Boot 4.x优化 | - | AOT性能提升 | - |
| 推荐使用 | 简单值 | 复杂配置 | 动态场景 |
💡 最佳实践:
- 简单配置项 → 使用
@Value- 一组相关配置 → 使用
@ConfigurationProperties(Spring Boot 4.x 强烈推荐)- 动态/编程式读取 → 使用
Environment
四、配置文件优先级(11层详解)
4.1 为什么需要优先级?
Spring Boot允许配置在多个位置,当同一配置项在多处定义时,优先级高的会覆盖优先级低的。
4.2 完整优先级列表(从高到低)
bash
┌─────────────────────────────────────────────────────────────────┐
│ 优先级 1:命令行参数 │
│ 示例:java -jar app.jar --server.port=9000 │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 2:SPRING_APPLICATION_JSON 环境变量 │
│ 示例:SPRING_APPLICATION_JSON='{"server.port":9000}' │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 3:ServletConfig 初始化参数 │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 4:ServletContext 初始化参数 │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 5:JNDI 属性(java:comp/env) │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 6:Java 系统属性(-D) │
│ 示例:java -Dserver.port=9000 -jar app.jar │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 7:操作系统环境变量 │
│ 示例:export SERVER_PORT=9000 │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 8:随机值(random.*) │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 9:JAR包外部的 application-{profile}.properties/yml │
│ 示例:./config/application-prod.yml │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 10:JAR包外部的 application.properties/yml │
│ 示例:./application.properties │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 11:JAR包内部的 application-{profile}.properties/yml │
│ 示例:classpath:application-prod.yml │
├─────────────────────────────────────────────────────────────────┤
│ 优先级 12:JAR包内部的 application.properties/yml │
│ 示例:classpath:application.yml │
└─────────────────────────────────────────────────────────────────┘
4.3 实战:验证配置文件优先级
步骤1:创建测试Controller
路径:config-app/src/main/java/com/example/config/controller/PriorityController.java
java
package com.example.config.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 配置文件优先级测试
*/
@RestController
public class PriorityController {
@Value("${test.priority:未设置}")
private String testPriority;
@Autowired
private Environment environment;
/**
* 查看当前生效的配置
* GET /api/priority/test
*/
@GetMapping("/priority/test")
public Map<String, Object> testPriority() {
Map<String, Object> result = new HashMap<>();
result.put("testPriority", testPriority);
result.put("serverPort", environment.getProperty("server.port"));
result.put("activeProfiles", environment.getActiveProfiles());
return result;
}
}
步骤2:在application.yml中添加测试配置
yaml
test:
priority: 来自classpath/application.yml(优先级最低)
步骤3:测试不同优先级的配置
bash
# 1. 默认启动(读取classpath配置)
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自classpath/application.yml(优先级最低)"
# 2. 使用命令行参数(优先级最高)
java -jar config-app.jar --test.priority=来自命令行参数
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自命令行参数"
# 3. 使用系统属性
java -Dtest.priority=来自系统属性 -jar config-app.jar
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自系统属性"
# 4. 使用环境变量
export TEST_PRIORITY=来自环境变量
java -jar config-app.jar
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自环境变量"
4.4 外部化配置目录结构
Spring Boot 4.x 继续支持以下目录加载外部配置文件(优先级从高到低):
bash
应用启动目录/
├── config/ # 优先级最高
│ ├── application.yml
│ └── application-prod.yml
│
├── config/optional/ # 可选配置目录
│ └── application.yml
│
├── ./ # 当前目录
│ ├── application.yml
│ └── application-prod.yml
│
└── classpath:/ # JAR包内部(优先级最低)
├── application.yml
└── application-prod.yml
💡 生产部署建议 :将配置文件放在JAR包外部的
config/目录,修改配置无需重新打包。
五、多环境配置(Profile)
5.1 为什么需要多环境?
实际项目中,不同环境需要不同配置:
| 配置项 | 开发环境(dev) | 测试环境(test) | 生产环境(prod) |
|---|---|---|---|
| 数据库URL | localhost | test-db | prod-db |
| 日志级别 | DEBUG | INFO | WARN |
| 缓存开关 | 关闭 | 开启 | 开启 |
| API密钥 | 测试密钥 | 测试密钥 | 生产密钥 |
5.2 创建多环境配置文件
步骤1:创建开发环境配置
路径:config-app/src/main/resources/application-dev.yml
yaml
# ===========================================
# 开发环境配置
# ===========================================
spring:
config:
activate:
on-profile: dev
# 服务器配置
server:
port: 8080
# 数据库配置
datasource:
url: jdbc:mysql://localhost:3306/dev_db?useSSL=false
username: dev_user
password: dev_password
# 日志配置
logging:
level:
root: DEBUG
com.example: DEBUG
file:
name: logs/dev-app.log
# 自定义配置
app:
name: 我的应用-开发环境
debug: true
cache:
enabled: false
# 第三方服务(使用Mock)
external:
api:
url: http://localhost:9999/mock
timeout: 5000
步骤2:创建测试环境配置
路径:config-app/src/main/resources/application-test.yml
yaml
# ===========================================
# 测试环境配置
# ===========================================
spring:
config:
activate:
on-profile: test
# 服务器配置
server:
port: 8081
# 数据库配置
datasource:
url: jdbc:mysql://test-db-server:3306/test_db?useSSL=false
username: test_user
password: test_password
# 日志配置
logging:
level:
root: INFO
com.example: INFO
file:
name: logs/test-app.log
# 自定义配置
app:
name: 我的应用-测试环境
debug: false
cache:
enabled: true
# 第三方服务(测试环境)
external:
api:
url: https://test-api.example.com
timeout: 10000
步骤3:创建生产环境配置
路径:config-app/src/main/resources/application-prod.yml
yaml
# ===========================================
# 生产环境配置
# ===========================================
spring:
config:
activate:
on-profile: prod
# 服务器配置
server:
port: 80
# 数据库配置
datasource:
url: jdbc:mysql://prod-db-cluster:3306/prod_db?useSSL=true
username: ${DB_USERNAME} # 从环境变量读取
password: ${DB_PASSWORD} # 从环境变量读取
# 日志配置
logging:
level:
root: WARN
com.example: INFO
file:
name: /var/logs/app.log
max-size: 100MB
max-history: 90
# 自定义配置
app:
name: 我的应用
debug: false
cache:
enabled: true
ttl: 3600
# 第三方服务(生产环境)
external:
api:
url: https://api.example.com
timeout: 30000
api-key: ${API_KEY} # 从环境变量读取
步骤4:配置主文件激活的Profile
路径:config-app/src/main/resources/application.yml
yaml
# ===========================================
# 主配置文件(公共配置)
# ===========================================
spring:
application:
name: config-app
# 默认激活的Profile
profiles:
active: dev
# 服务器公共配置
server:
servlet:
context-path: /api
# 公共自定义配置
app:
version: 1.0.0
description: Spring Boot 4.x配置文件演示应用
owner: 开发团队
contact:
email: support@example.com
phone: 400-888-8888
# Actuator 监控配置
management:
endpoints:
web:
exposure:
include: health,info,configprops,env
endpoint:
health:
show-details: always
5.3 激活Profile的四种方式
方式一:在application.yml中指定(开发方便)
yaml
spring:
profiles:
active: dev
方式二:命令行参数(部署灵活)
bash
java -jar config-app.jar --spring.profiles.active=prod
方式三:环境变量(容器化推荐)
bash
export SPRING_PROFILES_ACTIVE=prod
java -jar config-app.jar
方式四:IDEA运行配置(本地开发)
bash
Run → Edit Configurations → Active profiles → 输入 dev/test/prod
5.4 创建Profile测试Controller
步骤1:创建Profile测试Controller
路径:config-app/src/main/java/com/example/config/controller/ProfileController.java
java
package com.example.config.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
/**
* Profile环境测试
*/
@RestController
public class ProfileController {
@Value("${app.name:未知}")
private String appName;
@Value("${app.debug:false}")
private boolean debug;
@Value("${server.port:8080}")
private Integer serverPort;
@Autowired
private Environment environment;
/**
* 查看当前环境信息
* GET /api/profile/info
*/
@GetMapping("/profile/info")
public Map<String, Object> getProfileInfo() {
Map<String, Object> result = new HashMap<>();
result.put("appName", appName);
result.put("debug", debug);
result.put("serverPort", serverPort);
result.put("activeProfiles", Arrays.asList(environment.getActiveProfiles()));
result.put("defaultProfiles", Arrays.asList(environment.getDefaultProfiles()));
return result;
}
/**
* 查看数据库配置(不同环境不同)
* GET /api/profile/database
*/
@GetMapping("/profile/database")
public Map<String, Object> getDatabaseConfig() {
Map<String, Object> result = new HashMap<>();
result.put("url", environment.getProperty("spring.datasource.url"));
result.put("username", environment.getProperty("spring.datasource.username"));
return result;
}
/**
* 查看外部API配置
* GET /api/profile/external-api
*/
@GetMapping("/profile/external-api")
public Map<String, Object> getExternalApiConfig() {
Map<String, Object> result = new HashMap<>();
result.put("url", environment.getProperty("external.api.url"));
result.put("timeout", environment.getProperty("external.api.timeout"));
return result;
}
}
步骤2:测试不同环境
bash
# 启动开发环境
java -jar config-app.jar --spring.profiles.active=dev
curl http://localhost:8080/api/profile/info
# 响应:activeProfiles = ["dev"], serverPort = 8080
# 启动测试环境
java -jar config-app.jar --spring.profiles.active=test
curl http://localhost:8081/api/profile/info
# 响应:activeProfiles = ["test"], serverPort = 8081
# 启动生产环境
java -jar config-app.jar --spring.profiles.active=prod
curl http://localhost:80/api/profile/info
# 响应:activeProfiles = ["prod"], serverPort = 80
5.5 多Profile同时激活
Spring Boot 4.x支持同时激活多个Profile:
bash
# 同时激活prod和monitoring profile
java -jar config-app.jar --spring.profiles.active=prod,monitoring
# 或使用逗号分隔
java -jar config-app.jar --spring.profiles.active=prod,monitoring,security
创建监控Profile:
路径:config-app/src/main/resources/application-monitoring.yml
yaml
spring:
config:
activate:
on-profile: monitoring
# 监控特定配置
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
metrics:
enabled: true
prometheus:
enabled: true
六、配置加密与安全存储
6.1 为什么需要配置加密?
生产环境中,数据库密码、API密钥等敏感信息不能明文存储:
yaml
# ❌ 不安全:明文密码
spring:
datasource:
password: 123456
# ✅ 安全:加密密码
spring:
datasource:
password: ENC(加密后的密文)
6.2 使用Jasypt进行配置加密
步骤1:添加Jasypt依赖
在config-app/pom.xml中添加:
xml
<!-- Jasypt加密依赖 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
步骤2:生成加密密码
创建加密工具类:
路径:config-app/src/test/java/com/example/config/JasyptTest.java
java
package com.example.config;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.junit.jupiter.api.Test;
/**
* Jasypt加密测试工具
*/
public class JasyptTest {
@Test
public void testEncrypt() {
// 创建加密器
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
// 设置密钥(生产环境应从环境变量读取)
config.setPassword("my-secret-key");
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
// 加密密码
String plainPassword = "123456";
String encryptedPassword = encryptor.encrypt(plainPassword);
System.out.println("原始密码: " + plainPassword);
System.out.println("加密后: ENC(" + encryptedPassword + ")");
// 验证解密
String decryptedPassword = encryptor.decrypt(encryptedPassword);
System.out.println("解密后: " + decryptedPassword);
}
}
步骤3:运行测试生成加密值
bash
mvn test -Dtest=JasyptTest
# 建议可以直接再IDEA 点左边的箭头来跑,不需要使用命令
输出示例:
scss
原始密码:123456
加密后:ENC(xK8j3mN9pL2qR5tY)
解密后:123456
步骤4:在配置文件中使用加密值
路径:config-app/src/main/resources/application-prod.yml
yaml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/prod_db
username: prod_user
password: ENC(xK8j3mN9pL2qR5tY) # 加密后的密码
步骤5:配置解密密钥
方式一:在application.yml中配置(不推荐生产环境)
yaml
jasypt:
encryptor:
password: my-secret-key
方式二:命令行参数(推荐)
bash
java -jar config-app.jar --jasypt.encryptor.password=my-secret-key
方式三:环境变量(最安全,推荐)
bash
export JASYPT_ENCRYPTOR_PASSWORD=my-secret-key
java -jar config-app.jar
6.3 创建加密配置测试Controller
路径:config-app/src/main/java/com/example/config/controller/EncryptController.java
java
package com.example.config.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 加密配置测试
*/
@RestController
public class EncryptController {
@Value("${spring.datasource.password:未配置}")
private String dbPassword;
/**
* 查看数据库密码(生产环境应脱敏)
* GET /api/encrypt/db-password
*/
@GetMapping("/encrypt/db-password")
public Map<String, Object> getDbPassword() {
Map<String, Object> result = new HashMap<>();
// 生产环境不应返回完整密码
if (dbPassword != null && dbPassword.length() > 4) {
result.put("password", dbPassword.substring(0, 2) + "****");
} else {
result.put("password", "已配置");
}
result.put("configured", dbPassword != null && !dbPassword.contains("未配置"));
return result;
}
}
七、配置校验(JSR380)
7.1 使用@Validated校验配置
步骤1:添加校验依赖
在config-app/pom.xml中添加:
xml
<!-- 参数校验依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
步骤2:创建带校验的配置类
路径:config-app/src/main/java/com/example/config/properties/ValidatedProperties.java
java
package com.example.config.properties;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
/**
* 带校验的配置属性类
* Spring Boot 4.x 基于 Jakarta EE 11
*/
@Component
@ConfigurationProperties(prefix = "app.security")
@Validated
public class ValidatedProperties {
/**
* 管理员邮箱(必须填写,且是有效邮箱)
*/
@NotBlank(message = "管理员邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String adminEmail;
/**
* 会话超时时间(分钟)
*/
@NotNull(message = "会话超时时间不能为空")
@Min(value = 5, message = "会话超时时间最小5分钟")
@Max(value = 1440, message = "会话超时时间最大1440分钟")
private Integer sessionTimeout;
/**
* 最大登录尝试次数
*/
@NotNull(message = "最大登录尝试次数不能为空")
@Min(value = 1, message = "最大登录尝试次数最小1次")
@Max(value = 10, message = "最大登录尝试次数最大10次")
private Integer maxLoginAttempts;
// ========== Getter 和 Setter ==========
public String getAdminEmail() { return adminEmail; }
public void setAdminEmail(String adminEmail) { this.adminEmail = adminEmail; }
public Integer getSessionTimeout() { return sessionTimeout; }
public void setSessionTimeout(Integer sessionTimeout) { this.sessionTimeout = sessionTimeout; }
public Integer getMaxLoginAttempts() { return maxLoginAttempts; }
public void setMaxLoginAttempts(Integer maxLoginAttempts) { this.maxLoginAttempts = maxLoginAttempts; }
@Override
public String toString() {
return "ValidatedProperties{" +
"adminEmail='" + adminEmail + '\'' +
", sessionTimeout=" + sessionTimeout +
", maxLoginAttempts=" + maxLoginAttempts +
'}';
}
}
步骤3:在application.yml中添加配置
yaml
app:
security:
admin-email: admin@example.com
session-timeout: 30
max-login-attempts: 5
步骤4:创建校验测试Controller
路径:config-app/src/main/java/com/example/config/controller/ValidationController.java
java
package com.example.config.controller;
import com.example.config.properties.ValidatedProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 配置校验测试
*/
@RestController
public class ValidationController {
@Autowired
private ValidatedProperties validatedProperties;
/**
* 获取安全配置
* GET /api/validation/security
*/
@GetMapping("/validation/security")
public Map<String, Object> getSecurityConfig() {
Map<String, Object> result = new HashMap<>();
result.put("adminEmail", validatedProperties.getAdminEmail());
result.put("sessionTimeout", validatedProperties.getSessionTimeout());
result.put("maxLoginAttempts", validatedProperties.getMaxLoginAttempts());
return result;
}
}
步骤5:测试校验效果
修改配置为无效值:
yaml
app:
security:
admin-email: invalid-email # 无效邮箱格式
session-timeout: 2 # 小于最小值5
max-login-attempts: 15 # 大于最大值10
启动应用时会报错:
yaml
Failed to bind properties under 'app.security' to com.example.config.properties.ValidatedProperties:
Property: app.security.admin-email
Value: invalid-email
Reason: 邮箱格式不正确
八、完整项目结构
bash
spring-boot-config-demo/
├── pom.xml # 父项目POM
│
└── config-app/
├── pom.xml # 子模块POM
└── src/
├── main/
│ ├── java/com/example/config/
│ │ ├── ConfigApplication.java
│ │ ├── properties/
│ │ │ ├── AppProperties.java
│ │ │ ├── ValidatedProperties.java
│ │ │ └── RefreshProperties.java
│ │ └── controller/
│ │ ├── ConfigController.java
│ │ ├── AppPropertiesController.java
│ │ ├── EnvironmentController.java
│ │ ├── PriorityController.java
│ │ ├── ProfileController.java
│ │ ├── EncryptController.java
│ │ └── ValidationController.java
│ │
│ └── resources/
│ ├── application.yml # 主配置文件
│ ├── application-dev.yml # 开发环境
│ ├── application-test.yml # 测试环境
│ └── application-prod.yml # 生产环境
│
└── test/
└── java/com/example/config/
└── JasyptTest.java # 加密测试
九、常见坑点与解决方案
❌ 坑点1:YAML缩进错误
现象 :启动报错Invalid yaml syntax
原因:YAML对缩进敏感,必须使用空格(不能用Tab)
解决方案:
yaml
# ❌ 错误:使用Tab或缩进不一致
app:
name: 测试 # Tab缩进
version: 1.0 # 空格缩进
# ✅ 正确:统一使用2个空格
app:
name: 测试
version: 1.0
❌ 坑点2:@ConfigurationProperties不生效
现象:配置类属性全是null
原因 :忘记添加@Component或@EnableConfigurationProperties
解决方案:
java
// 方式1:添加@Component
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties { }
// 方式2:启动类添加@EnableConfigurationProperties
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class ConfigApplication { }
❌ 坑点3:Profile未激活
现象:多环境配置未生效
原因:未指定active profile
解决方案:
yaml
# 在application.yml中指定
spring:
profiles:
active: dev
# 或命令行指定
java -jar app.jar --spring.profiles.active=prod
❌ 坑点4:Java版本不兼容
现象:Spring Boot 4.x启动报错
原因:Java版本低于17
解决方案:
bash
# 检查Java版本
java -version
# 升级到Java 21(推荐)
# 下载:https://adoptium.net/
❌ 坑点5:加密配置解密失败
现象 :启动报错Unable to decrypt property
原因:解密密钥未正确配置
解决方案:
bash
# 通过环境变量传递密钥(推荐)
export JASYPT_ENCRYPTOR_PASSWORD=my-secret-key
java -jar app.jar
# 或命令行参数
java -jar app.jar --jasypt.encryptor.password=my-secret-key
十、最佳实践总结
10.1 配置文件组织规范
| 文件 | 用途 | 提交Git |
|---|---|---|
application.yml |
公共配置 | ✅ 是 |
application-dev.yml |
开发环境 | ✅ 是 |
application-test.yml |
测试环境 | ✅ 是 |
application-prod.yml |
生产环境 | ❌ 否(敏感信息) |
application-local.yml |
本地个人配置 | ❌ 否(加入.gitignore) |
10.2 配置读取方式选择
| 场景 | 推荐方式 |
|---|---|
| 单个简单配置 | @Value |
| 一组相关配置 | @ConfigurationProperties |
| 动态/编程式读取 | Environment |
| 需要校验的配置 | @ConfigurationProperties + @Validated |
10.3 Spring Boot 4.x 安全配置建议
yaml
# ✅ 推荐:敏感信息从环境变量读取
spring:
datasource:
password: ${DB_PASSWORD}
# ✅ 推荐:使用加密
spring:
datasource:
password: ENC(加密密文)
# ❌ 避免:明文密码
spring:
datasource:
password: 123456
10.4 Spring Boot 4.x 新特性利用
| 特性 | 使用方式 | 收益 |
|---|---|---|
| JSpecify空安全 | 添加jspecify依赖 | 编译期空指针检查 |
| YAML锚点 | &defaults 和 *defaults |
配置复用,减少重复 |
| AOT优化 | Native Image编译 | 启动速度提升5-10倍 |
| 模块化自动配置 | 按需引入starter | 减小JAR包体积 |
十一、本篇延伸思考
💡 思考题:
- 为什么
@ConfigurationProperties比@Value更推荐?- Spring Boot 4.x 对Java版本要求提高的原因是什么?
- 生产环境如何安全地管理配置密钥?
- 配置文件的11层优先级中,哪几层最常用?
- 如何在Docker/K8s环境中管理Spring Boot 4.x配置?
十二、下篇预告
第4篇:《Spring Boot 4.x日志系统全攻略:Logback配置与ELK集成》
将深入探讨:
- Logback配置文件详解
- 日志级别与输出格式定制
- 日志文件滚动策略
- 集成ELK(Elasticsearch + Logstash + Kibana)
- 分布式链路追踪(OpenTelemetry)
- Spring Boot 4.x 可观测性增强
附录:快速测试命令(一般如果使用IDEA 编辑器,都不需要,直接在编辑器里面跑就行,命令行要注意java版本)
bash
# 1. 检查Java版本(需要Java 21+)
java -version
# 2. 构建项目
cd spring-boot-config-demo
mvn clean install
# 3. 启动开发环境
cd config-app
mvn spring-boot:run
# 4. 启动生产环境
java -jar target/config-app-1.0.0.jar --spring.profiles.active=prod
# 5. 测试配置接口
curl http://localhost:8080/api/config/value
curl http://localhost:8080/api/config/properties
curl http://localhost:8080/api/profile/info
curl http://localhost:8080/api/validation/security
# 6. 查看Actuator配置
curl http://localhost:8080/api/actuator/configprops
curl http://localhost:8080/api/actuator/env
# 7. 测试多环境
curl http://localhost:8080/api/profile/info
# 修改profile后重启,再次测试
所有示例代码已上传GitHub:注意(git仓库代码可能跟文章有些小出入,大家可以githup代码为准,如有疑问,可以关注公众号并留言咨询)
ruby
主仓库:https://github.com/beatafu/spring-boot-learning.git
本教程:[https://github.com/beatafu/spring-boot-learning/tree/main/03-starter-deep-dive](https://github.com/beatafu/spring-boot-learning/tree/main/03-spring-boot-config-demo)
Spring Boot 4.x 版本对照表
| 配置项 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| 最低Java版本 | 17 | 17(推荐21+) |
| Spring Framework | 6.x | 7.x |
| Jakarta EE | 10 | 11 |
| 配置元数据 | spring-configuration-metadata.json | 增强版(支持JSpecify) |
| 自动配置 | 单体 | 47个轻量模块 |
| AOT支持 | 初步支持 | 全面优化 |
| 虚拟线程 | 实验性 | 生产就绪 |
🎉 恭喜你完成第三篇! 现在你已经:
- ✅ 掌握properties和yml两种配置格式
- ✅ 理解配置文件的11层优先级
- ✅ 能使用三种方式读取配置
- ✅ 能配置多环境Profile
- ✅ 能进行配置加密和校验
- ✅ 了解Spring Boot 4.x新特性
建议:动手完成所有示例代码,特别是多环境配置和加密配置,这是生产环境必备技能!
作者 :架构师Beata
日期 :2026年3月18日
声明 :本文基于网络文档整理,如有疏漏,欢迎指正。转载请注明出处。
互动 :代码完全经过自测,如有任何问题?欢迎在评论区分享