Spring Boot 配置体系详解
一、知识概述
Spring Boot 提供了灵活而强大的配置体系,支持多种配置方式和配置源。开发者可以根据不同环境和需求,灵活管理应用配置。配置体系的核心包括:
- 配置文件格式:Properties、YAML
- 多环境配置:Profile 机制
- 配置属性绑定:类型安全的配置
- 配置加载顺序:优先级规则
- 外部化配置:配置中心、环境变量
理解 Spring Boot 配置体系,是管理应用配置的关键。
二、知识点详细讲解
2.1 配置文件格式
Properties 格式
app.name=MyApp
app.port=8080
app.security.enabled=true
app.database.url=jdbc:mysql://localhost:3306/mydb
YAML 格式
app:
name: MyApp
port: 8080
security:
enabled: true
database:
url: jdbc:mysql://localhost:3306/mydb
YAML 优势
- 层次结构清晰
- 支持复杂对象
- 支持列表和数组
- 可读性更强
2.2 配置文件位置与加载顺序
Spring Boot 按以下顺序加载配置文件(后者覆盖前者):
1. jar 包外部的 application.properties/yml
2. jar 包内部的 application.properties/yml
3. @Configuration 注解类上的 @PropertySource
4. 默认属性(SpringApplication.setDefaultProperties)
具体目录:
当前目录的 /config 子目录
当前目录
classpath 的 /config 包
classpath 根目录
2.3 Profile 多环境配置
Profile 文件命名
application.yml # 默认配置
application-dev.yml # 开发环境
application-test.yml # 测试环境
application-prod.yml # 生产环境
激活 Profile
# application.yml
spring:
profiles:
active: dev
或命令行:
java -jar app.jar --spring.profiles.active=prod
2.4 配置属性绑定
@Value 注入
@Value("${app.name}")
private String appName;
@ConfigurationProperties 绑定
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private int port;
// getter/setter
}
2.5 配置加载优先级
从高到低:
- 命令行参数
- JNDI 属性
- Java 系统属性
- 环境变量
- jar 外部配置文件
- jar 内部配置文件
- @PropertySource
- 默认属性
2.6 配置属性松散绑定
Spring Boot 支持多种命名格式:
app:
user-name: John # 驼峰形式
user_name: John # 下划线形式
user-name: John # 短横线形式
USERNAME: John # 大写形式
对应 Java 属性:
private String userName; // 以上四种格式都可以绑定
三、代码示例
3.1 基础配置示例
# application.yml - 主配置文件
# 应用配置
spring:
application:
name: my-application
# 环境
profiles:
active: dev
# 数据源配置
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
# JPA 配置
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
# Redis 配置
data:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms
# 缓存配置
cache:
type: redis
redis:
time-to-live: 3600000
# Web 配置
web:
resources:
cache:
period: 3600
# 文件上传配置
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 100MB
# JSON 配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
# 服务器配置
server:
port: 8080
servlet:
context-path: /api
tomcat:
max-threads: 200
accept-count: 100
# 日志配置
logging:
level:
root: INFO
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
# 自定义配置
app:
name: My Application
version: 1.0.0
security:
enabled: true
secret-key: my-secret-key
token-validity: 3600
features:
- feature1
- feature2
- feature3
3.2 多环境配置示例
# application.yml - 公共配置
spring:
application:
name: my-application
# 公共配置
app:
name: ${spring.application.name}
default-timeout: 30000
---
# application-dev.yml - 开发环境
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:mysql://localhost:3306/mydb_dev
username: dev
password: dev123
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
server:
port: 8080
logging:
level:
com.example: DEBUG
app:
debug: true
cache:
enabled: false
---
# application-test.yml - 测试环境
spring:
config:
activate:
on-profile: test
datasource:
url: jdbc:mysql://test-server:3306/mydb_test
username: test
password: test123
jpa:
show-sql: false
hibernate:
ddl-auto: validate
server:
port: 8081
logging:
level:
com.example: INFO
app:
debug: false
cache:
enabled: true
---
# application-prod.yml - 生产环境
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-server:3306/mydb_prod
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
show-sql: false
hibernate:
ddl-auto: none
server:
port: 80
logging:
level:
root: WARN
com.example: INFO
app:
debug: false
cache:
enabled: true
ttl: 7200
3.3 属性绑定示例
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.*;
import java.util.*;
// 简单属性绑定
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private int defaultTimeout = 30000;
private boolean debug = false;
private final Security security = new Security();
private final Cache cache = new Cache();
private final Features features = new Features();
// 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 int getDefaultTimeout() { return defaultTimeout; }
public void setDefaultTimeout(int defaultTimeout) { this.defaultTimeout = defaultTimeout; }
public boolean isDebug() { return debug; }
public void setDebug(boolean debug) { this.debug = debug; }
public Security getSecurity() { return security; }
public Cache getCache() { return cache; }
public Features getFeatures() { return features; }
// 安全配置
public static class Security {
private boolean enabled = true;
@NotBlank
private String secretKey;
@Min(60)
private long tokenValidity = 3600;
private final Jwt jwt = new Jwt();
// getter/setter
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getSecretKey() { return secretKey; }
public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
public long getTokenValidity() { return tokenValidity; }
public void setTokenValidity(long tokenValidity) { this.tokenValidity = tokenValidity; }
public Jwt getJwt() { return jwt; }
public static class Jwt {
private String header = "Authorization";
private String prefix = "Bearer ";
// getter/setter
public String getHeader() { return header; }
public void setHeader(String header) { this.header = header; }
public String getPrefix() { return prefix; }
public void setPrefix(String prefix) { this.prefix = prefix; }
}
}
// 缓存配置
public static class Cache {
private boolean enabled = true;
private String type = "redis";
private long ttl = 3600;
private int maxSize = 1000;
// getter/setter
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public long getTtl() { return ttl; }
public void setTtl(long ttl) { this.ttl = ttl; }
public int getMaxSize() { return maxSize; }
public void setMaxSize(int maxSize) { this.maxSize = maxSize; }
}
// 功能开关配置
public static class Features {
private List<String> enabled = new ArrayList<>();
private Map<String, Boolean> switches = new HashMap<>();
// getter/setter
public List<String> getEnabled() { return enabled; }
public void setEnabled(List<String> enabled) { this.enabled = enabled; }
public Map<String, Boolean> getSwitches() { return switches; }
public void setSwitches(Map<String, Boolean> switches) { this.switches = switches; }
public boolean isEnabled(String featureName) {
return enabled.contains(featureName) ||
switches.getOrDefault(featureName, false);
}
}
}
// 启用配置属性
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
}
// 使用配置属性
@Service
public class FeatureService {
private final AppProperties properties;
public FeatureService(AppProperties properties) {
this.properties = properties;
}
public void checkFeatures() {
System.out.println("应用名称: " + properties.getName());
System.out.println("安全启用: " + properties.getSecurity().isEnabled());
System.out.println("缓存类型: " + properties.getCache().getType());
if (properties.getFeatures().isEnabled("feature1")) {
System.out.println("feature1 已启用");
}
}
}
3.4 @Value 注入示例
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ValueInjectionService {
// 注入简单值
@Value("${app.name}")
private String appName;
// 注入默认值
@Value("${app.description:Default Description}")
private String description;
// 注入数值
@Value("${server.port}")
private int port;
// 注入布尔值
@Value("${app.debug:false}")
private boolean debug;
// 注入列表
@Value("${app.features}")
private List<String> features;
// 注入系统属性
@Value("${java.home}")
private String javaHome;
// 注入环境变量
@Value("${HOME:unknown}")
private String homeDir;
// SpEL 表达式
@Value("#{systemProperties['user.name']}")
private String userName;
// SpEL 数学运算
@Value("#{2 * 1024}")
private int calculatedValue;
// SpEL 引用其他 Bean
@Value("#{appProperties.name}")
private String appNameFromBean;
// SpEL 条件表达式
@Value("#{environment['spring.profiles.active'] == 'prod' ? 'production' : 'development'}")
private String environment;
public void printValues() {
System.out.println("应用名称: " + appName);
System.out.println("描述: " + description);
System.out.println("端口: " + port);
System.out.println("调试模式: " + debug);
System.out.println("功能列表: " + features);
System.out.println("Java Home: " + javaHome);
System.out.println("用户目录: " + homeDir);
System.out.println("用户名: " + userName);
System.out.println("计算值: " + calculatedValue);
System.out.println("环境: " + environment);
}
}
3.5 条件化配置示例
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.Value;
@Configuration
public class ConditionalConfig {
// 根据属性值创建 Bean
@Bean
@ConditionalOnProperty(
prefix = "app.cache",
name = "type",
havingValue = "redis"
)
public CacheService redisCacheService() {
System.out.println("创建 Redis 缓存服务");
return new RedisCacheService();
}
@Bean
@ConditionalOnProperty(
prefix = "app.cache",
name = "type",
havingValue = "local",
matchIfMissing = true
)
public CacheService localCacheService() {
System.out.println("创建本地缓存服务");
return new LocalCacheService();
}
// 根据 Profile 创建 Bean
@Bean
@Profile("dev")
public DataSource devDataSource() {
System.out.println("创建开发环境数据源");
return new DevDataSource();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
System.out.println("创建生产环境数据源");
return new ProdDataSource();
}
// 根据属性是否存在创建 Bean
@Bean
@ConditionalOnProperty(prefix = "app.security", name = "secret-key")
public SecurityService securityService() {
System.out.println("创建安全服务");
return new SecurityService();
}
}
// Profile 条件示例
@Configuration
public class ProfileBasedConfig {
@Bean
@Profile({"dev", "test"})
public MockDataService mockDataService() {
return new MockDataService();
}
@Bean
@Profile("prod")
public RealDataService realDataService() {
return new RealDataService();
}
}
3.6 配置刷新示例
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
// 支持配置刷新的 Bean
@Service
@RefreshScope
@ConfigurationProperties(prefix = "app.dynamic")
public class DynamicConfig {
private String feature1;
private boolean feature2Enabled;
private int maxConnections;
// getter/setter
public String getFeature1() { return feature1; }
public void setFeature1(String feature1) { this.feature1 = feature1; }
public boolean isFeature2Enabled() { return feature2Enabled; }
public void setFeature2Enabled(boolean feature2Enabled) { this.feature2Enabled = feature2Enabled; }
public int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; }
public void printConfig() {
System.out.println("Feature1: " + feature1);
System.out.println("Feature2 Enabled: " + feature2Enabled);
System.out.println("Max Connections: " + maxConnections);
}
}
// 配置变更监听器
@Component
public class ConfigChangeListener {
@EventListener
public void onEnvironmentChangeEvent(EnvironmentChangeEvent event) {
System.out.println("配置变更: " + event.getKeys());
}
}
// 控制器查看配置
@RestController
@RequestMapping("/api/config")
@RefreshScope
public class ConfigController {
private final DynamicConfig dynamicConfig;
public ConfigController(DynamicConfig dynamicConfig) {
this.dynamicConfig = dynamicConfig;
}
@GetMapping
public Map<String, Object> getConfig() {
Map<String, Object> config = new HashMap<>();
config.put("feature1", dynamicConfig.getFeature1());
config.put("feature2Enabled", dynamicConfig.isFeature2Enabled());
config.put("maxConnections", dynamicConfig.getMaxConnections());
return config;
}
}
3.7 外部化配置示例
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
@Configuration
@PropertySource("classpath:config.properties")
public class ExternalConfig {
@Autowired
private Environment environment;
// 加载外部配置文件
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 加载多个配置文件
configurer.setLocations(
new ClassPathResource("config.properties"),
new ClassPathResource("config-override.properties")
);
// 忽略未找到的配置文件
configurer.setIgnoreResourceNotFound(true);
// 忽略无法解析的占位符
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
// 通过 Environment 访问配置
public void printEnvironmentConfig() {
System.out.println("应用名称: " + environment.getProperty("app.name"));
System.out.println("端口: " + environment.getProperty("server.port", Integer.class));
System.out.println("调试模式: " + environment.getProperty("app.debug", Boolean.class, false));
// 检查是否包含属性
if (environment.containsProperty("app.security.secret-key")) {
System.out.println("安全配置已设置");
}
// 获取激活的 Profile
String[] activeProfiles = environment.getActiveProfiles();
System.out.println("激活的 Profile: " + Arrays.toString(activeProfiles));
}
}
// 环境变量配置
@Service
public class EnvironmentConfigService {
private final Environment environment;
public EnvironmentConfigService(Environment environment) {
this.environment = environment;
}
public String getDatabaseUrl() {
// 优先使用环境变量,否则使用配置文件
return environment.getProperty("DATABASE_URL",
environment.getProperty("spring.datasource.url"));
}
public String getDatabaseUsername() {
return environment.getProperty("DB_USERNAME",
environment.getProperty("spring.datasource.username"));
}
public String getDatabasePassword() {
return environment.getProperty("DB_PASSWORD",
environment.getProperty("spring.datasource.password"));
}
}
四、实战应用场景
4.1 多数据源配置
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
import java.util.*;
// 多数据源配置属性
@ConfigurationProperties(prefix = "app.datasource")
public class MultiDataSourceProperties {
private Map<String, DataSourceConfig> sources = new HashMap<>();
public Map<String, DataSourceConfig> getSources() {
return sources;
}
public static class DataSourceConfig {
private String url;
private String username;
private String password;
private String driverClassName;
private int initialSize = 5;
private int maxActive = 20;
// getter/setter
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getDriverClassName() { return driverClassName; }
public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; }
public int getInitialSize() { return initialSize; }
public void setInitialSize(int initialSize) { this.initialSize = initialSize; }
public int getMaxActive() { return maxActive; }
public void setMaxActive(int maxActive) { this.maxActive = maxActive; }
}
}
// 动态数据源配置
@Configuration
@EnableConfigurationProperties(MultiDataSourceProperties.class)
public class DynamicDataSourceConfig {
@Bean
@Primary
public DataSource dynamicDataSource(MultiDataSourceProperties properties) {
Map<Object, Object> targetDataSources = new HashMap<>();
properties.getSources().forEach((name, config) -> {
targetDataSources.put(name, createDataSource(config));
});
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(
targetDataSources.get("primary")
);
return dataSource;
}
private DataSource createDataSource(MultiDataSourceProperties.DataSourceConfig config) {
// 创建数据源
return null;
}
}
// 数据源上下文
public class DataSourceContextHolder {
private static final ThreadLocal<String> context = new ThreadLocal<>();
public static void setDataSource(String name) {
context.set(name);
}
public static String getDataSource() {
return context.get();
}
public static void clear() {
context.remove();
}
}
// 动态数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
// 使用注解切换数据源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "primary";
}
// 切面处理
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
try {
DataSourceContextHolder.setDataSource(dataSource.value());
return point.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
}
配置示例
app:
datasource:
sources:
primary:
url: jdbc:mysql://localhost:3306/primary_db
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/secondary_db
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
4.2 配置加密
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.Value;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
// 加密配置
@Configuration
public class EncryptionConfig {
@Bean
public StandardPBEStringEncryptor encryptor(
@Value("${jasypt.encryptor.password}") String password) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setAlgorithm("PBEWithMD5AndDES");
return encryptor;
}
}
// 加密属性绑定
@ConfigurationProperties(prefix = "app")
public class SecureProperties {
// ENC() 标识加密值
@Value("${app.database.password}")
private String databasePassword;
@Value("${app.security.secret-key}")
private String secretKey;
// getter
}
// 配置文件
/*
app:
database:
password: ENC(加密后的密文)
security:
secret-key: ENC(加密后的密文)
jasypt:
encryptor:
password: ${JASYPT_PASSWORD}
*/
4.3 配置验证
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.*;
@ConfigurationProperties(prefix = "app")
@Validated
public class ValidatedProperties {
@NotBlank(message = "应用名称不能为空")
@Size(min = 2, max = 50, message = "应用名称长度必须在2-50之间")
private String name;
@Min(value = 1, message = "端口必须大于0")
@Max(value = 65535, message = "端口必须小于65535")
private int port = 8080;
@Email(message = "管理员邮箱格式不正确")
private String adminEmail;
@Pattern(regexp = "^https?://.*", message = "URL 必须以 http 或 https 开头")
private String callbackUrl;
@Valid
private final Database database = new Database();
// getter/setter
public static class Database {
@NotBlank
private String url;
@NotBlank
private String username;
@Size(min = 6, message = "密码长度至少6位")
private String password;
@Min(1)
@Max(100)
private int poolSize = 10;
// getter/setter
}
}
// 配置验证失败时,应用启动会抛出异常
五、总结与最佳实践
配置管理原则
-
敏感信息加密
- 密码、密钥等敏感信息加密存储
- 使用环境变量传递加密密钥
-
环境隔离
- 使用 Profile 分离不同环境配置
- 生产环境配置不要包含在代码中
-
配置验证
- 使用 JSR-303 注解验证配置
- 提供合理的默认值
-
配置文档化
- 添加配置属性注释
- 生成配置元数据
最佳实践
-
配置文件命名
- 使用统一的命名规范
- 配置项按功能分组
-
配置更新
- 使用 @RefreshScope 支持动态刷新
- 监听配置变更事件
-
配置中心
- 大型项目使用配置中心
- 支持灰度发布和回滚
Spring Boot 配置体系提供了灵活而强大的配置管理能力。掌握配置文件格式、属性绑定、多环境配置等核心概念,能够帮助开发者构建可配置、可维护的应用程序。