副标题:所有配置信息尽在我掌握! 🕵️♂️
🎬 开场白:配置都从哪儿来?
嘿,小伙伴们!👋 今天我们要聊一个Spring中超级强大的抽象——Environment!
想象这个场景:
- ⚙️ 配置文件里有配置
- 🔧 系统环境变量里有配置
- 💻 JVM参数里有配置
- 🎯 命令行参数里也有配置
如果配置冲突了怎么办?谁的优先级最高?
别慌!Spring的Environment就像一个"特工007"🕵️♂️,帮你管理所有配置!
📚 第一幕:什么是Environment?
核心概念
Environment是Spring 3.1引入的一个抽象,它代表了应用程序运行时的环境,包含两个关键方面:
- Profiles(环境配置):dev、test、prod等不同环境
- Properties(属性配置):所有的配置信息
public interface Environment extends PropertyResolver {
// Profile相关
String[] getActiveProfiles();
String[] getDefaultProfiles();
boolean acceptsProfiles(String... profiles);
// 继承自PropertyResolver的方法
boolean containsProperty(String key);
String getProperty(String key);
String getProperty(String key, String defaultValue);
<T> T getProperty(String key, Class<T> targetType);
}
生活比喻:
Environment就像你的智能管家🤵,他知道:
- 你家的环境(Profile):是工作日还是周末?
- 你的偏好(Properties):喜欢喝咖啡还是茶?
🎪 第二幕:配置来源大揭秘
PropertySource - 配置源
每个配置来源都是一个PropertySource:
public abstract class PropertySource<T> {
protected final String name; // 配置源名称
protected final T source; // 配置源对象
// 获取配置值
public abstract Object getProperty(String name);
}
Spring Boot的配置优先级(从高到低)
| 优先级 | 配置源 | 示例 | 图标 |
|---|---|---|---|
| 1 | 命令行参数 | java -jar app.jar --server.port=9090 | 🎯 |
| 2 | SPRING_APPLICATION_JSON | 环境变量中的JSON配置 | 📋 |
| 3 | ServletConfig初始化参数 | Web应用配置 | 🌐 |
| 4 | ServletContext初始化参数 | Web容器配置 | 🏪 |
| 5 | JNDI属性 | java:comp/env | 🔧 |
| 6 | JVM系统属性 | -Dserver.port=9090 | 💻 |
| 7 | 操作系统环境变量 | export SERVER_PORT=9090 | 🖥️ |
| 8 | RandomValuePropertySource | ${random.int} | 🎲 |
| 9 | jar包外的application-{profile}.properties | 外部配置文件 | 📦 |
| 10 | jar包内的application-{profile}.properties | 内部配置文件 | 📄 |
| 11 | jar包外的application.properties | 默认外部配置 | 📦 |
| 12 | jar包内的application.properties | 默认内部配置 | 📄 |
| 13 | @PropertySource注解的配置 | 自定义配置文件 | 🔖 |
| 14 | 默认属性 | SpringApplication.setDefaultProperties | ⚙️ |
记忆口诀:
命令行最牛逼 👑
JSON紧跟随 📋
系统环境排中间 💻🖥️
配置文件靠后站 📄
默认属性最底层 ⚙️
🎯 第三幕:实战案例 - 获取配置
方式1:直接注入Environment
@Component
public class ConfigDemo {
@Autowired
private Environment environment;
public void showConfig() {
// 获取简单配置
String appName = environment.getProperty("spring.application.name");
System.out.println("📛 应用名称:" + appName);
// 获取配置(带默认值)
String port = environment.getProperty("server.port", "8080");
System.out.println("🔌 端口号:" + port);
// 获取配置(指定类型)
Integer maxThreads = environment.getProperty(
"server.tomcat.max-threads",
Integer.class,
200
);
System.out.println("🧵 最大线程数:" + maxThreads);
// 检查配置是否存在
boolean hasDb = environment.containsProperty("spring.datasource.url");
System.out.println("🗄️ 配置了数据库?" + hasDb);
}
}
方式2:使用@Value注解
@Component
public class AppConfig {
// 注入简单值
@Value("${app.name}")
private String appName;
// 注入带默认值
@Value("${app.version:1.0.0}")
private String appVersion;
// 注入并转换类型
@Value("${app.max-users:1000}")
private Integer maxUsers;
// 使用SpEL表达式
@Value("#{${app.timeout:5000} + 1000}")
private Long timeout;
// 注入数组
@Value("${app.allowed-ips:127.0.0.1,192.168.1.1}")
private String[] allowedIps;
@PostConstruct
public void init() {
System.out.println("📛 应用名称:" + appName);
System.out.println("🔢 版本号:" + appVersion);
System.out.println("👥 最大用户数:" + maxUsers);
System.out.println("⏱️ 超时时间:" + timeout + "ms");
System.out.println("🌐 允许的IP:" + Arrays.toString(allowedIps));
}
}
方式3:使用@ConfigurationProperties
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private Integer maxUsers;
private Database database = new Database();
private Map<String, String> settings = new HashMap<>();
// 嵌套配置
public static class Database {
private String url;
private String username;
private String password;
private Integer poolSize = 10;
// getters and setters...
}
// getters and setters...
}
// 在application.yml中配置
/*
app:
name: MyApp
version: 2.0.0
max-users: 5000
database:
url: jdbc:mysql://localhost:3306/db
username: root
password: secret
pool-size: 20
settings:
feature-x: enabled
feature-y: disabled
*/
🌟 第四幕:Profile - 环境切换
什么是Profile?
Profile就像你的变身术🦸,让应用在不同环境下表现不同!
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
System.out.println("💻 创建开发环境数据源");
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:h2:mem:testdb");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
@Bean
@Profile("test")
public DataSource testDataSource() {
System.out.println("🧪 创建测试环境数据源");
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://test-server:3306/testdb");
dataSource.setUsername("test_user");
dataSource.setPassword("test_pass");
return dataSource;
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
System.out.println("🏭 创建生产环境数据源");
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://prod-server:3306/proddb");
dataSource.setUsername("prod_user");
dataSource.setPassword("prod_pass");
dataSource.setMaximumPoolSize(50);
return dataSource;
}
}
激活Profile的方式
方式1:配置文件中激活
# application.properties
spring.profiles.active=dev
# application.yml
spring:
profiles:
active: dev
方式2:命令行参数
# 方式A:使用--spring.profiles.active
java -jar app.jar --spring.profiles.active=prod
# 方式B:使用-Dspring.profiles.active
java -Dspring.profiles.active=prod -jar app.jar
方式3:环境变量
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar
方式4:编程方式
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
// 设置默认Profile
app.setAdditionalProfiles("dev");
app.run(args);
}
}
多Profile激活
# 同时激活多个Profile
java -jar app.jar --spring.profiles.active=prod,swagger
spring:
profiles:
active: prod,swagger
效果:
- ✅ 生产环境配置生效
- ✅ Swagger文档也启用
Profile表达式
// 非dev环境
@Profile("!dev")
public class ProdConfig {
// ...
}
// dev或test环境
@Profile({"dev", "test"})
public class NonProdConfig {
// ...
}
// dev并且debug
@Profile("dev & debug")
public class DevDebugConfig {
// ...
}
// prod或uat
@Profile("prod | uat")
public class ProductionLikeConfig {
// ...
}
🎨 第五幕:自定义PropertySource
场景:从数据库加载配置
/**
* 数据库配置源
*/
public class DatabasePropertySource extends PropertySource<Map<String, Object>> {
private final Map<String, Object> properties;
public DatabasePropertySource(String name, JdbcTemplate jdbcTemplate) {
super(name);
this.properties = loadPropertiesFromDatabase(jdbcTemplate);
}
@Override
public Object getProperty(String name) {
return properties.get(name);
}
/**
* 从数据库加载配置
*/
private Map<String, Object> loadPropertiesFromDatabase(JdbcTemplate jdbcTemplate) {
String sql = "SELECT config_key, config_value FROM sys_config";
Map<String, Object> props = new HashMap<>();
jdbcTemplate.query(sql, rs -> {
props.put(rs.getString("config_key"), rs.getString("config_value"));
});
System.out.println("🗄️ 从数据库加载了 " + props.size() + " 个配置");
return props;
}
}
注册自定义PropertySource
@Configuration
public class CustomPropertySourceConfig implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
// 创建数据源(这里简化处理)
JdbcTemplate jdbcTemplate = createJdbcTemplate();
// 创建自定义配置源
DatabasePropertySource databasePropertySource =
new DatabasePropertySource("databaseConfig", jdbcTemplate);
// 添加到配置源列表(最高优先级)
propertySources.addFirst(databasePropertySource);
System.out.println("✅ 已注册数据库配置源");
}
private JdbcTemplate createJdbcTemplate() {
// 创建数据源...
return new JdbcTemplate(dataSource);
}
}
在spring.factories中注册
# src/main/resources/META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\
com.example.config.CustomPropertySourceConfig
🔧 第六幕:占位符解析
${} 占位符
app:
name: MyApp
version: 1.0.0
full-name: ${app.name} v${app.version}
server:
port: 8080
url: http://localhost:${server.port}
解析后:
app.full-name = MyApp v1.0.0
server.url = http://localhost:8080
占位符默认值
@Value("${app.timeout:5000}")
private Long timeout; // 如果没配置,默认5000
@Value("${app.feature.enabled:false}")
private Boolean featureEnabled; // 默认false
@Value("${app.max-retries:#{3}}")
private Integer maxRetries; // 使用SpEL表达式作为默认值
复杂占位符解析
app:
env: ${ENVIRONMENT:dev}
db:
host: ${DB_HOST:localhost}
port: ${DB_PORT:3306}
name: ${DB_NAME:mydb}
url: jdbc:mysql://${app.db.host}:${app.db.port}/${app.db.name}
🎪 第七幕:实战场景
场景1:多环境配置文件
文件结构:
src/main/resources/
├── application.yml # 公共配置
├── application-dev.yml # 开发环境
├── application-test.yml # 测试环境
└── application-prod.yml # 生产环境
application.yml(公共配置)
spring:
application:
name: my-app
profiles:
active: @spring.profiles.active@ # Maven占位符
app:
common-config: 这是所有环境共享的配置
application-dev.yml(开发环境)
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
logging:
level:
root: DEBUG
com.example: TRACE
app:
env-specific: 这是开发环境的配置
application-prod.yml(生产环境)
server:
port: 80
spring:
datasource:
url: jdbc:mysql://prod-server:3306/proddb
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
logging:
level:
root: WARN
com.example: INFO
app:
env-specific: 这是生产环境的配置
场景2:配置加密
@Configuration
public class EncryptedPropertyConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
// 设置占位符解析器
configurer.setPlaceholderPrefix("${");
configurer.setPlaceholderSuffix("}");
// 设置值解析器(解密)
configurer.setValueResolver(placeholderName -> {
// 如果是加密的值,解密
if (placeholderName.startsWith("encrypted:")) {
String encryptedValue = placeholderName.substring(10);
return decrypt(encryptedValue);
}
return null;
});
return configurer;
}
private static String decrypt(String encrypted) {
// 实现解密逻辑
System.out.println("🔓 解密配置:" + encrypted);
return "decrypted_" + encrypted;
}
}
使用:
app:
password: ${encrypted:MTIzNDU2}
场景3:动态刷新配置
@Component
@RefreshScope // 支持配置刷新
public class DynamicConfig {
@Value("${app.dynamic-value}")
private String dynamicValue;
public String getDynamicValue() {
return dynamicValue;
}
}
@RestController
public class ConfigController {
@Autowired
private DynamicConfig dynamicConfig;
@GetMapping("/config")
public String getConfig() {
return "当前配置:" + dynamicConfig.getDynamicValue();
}
}
刷新配置:
# 使用Spring Boot Actuator刷新
curl -X POST http://localhost:8080/actuator/refresh
🔍 第八幕:Environment源码解析
Environment继承体系
Environment (接口)
↓
ConfigurableEnvironment (接口)
↓
AbstractEnvironment (抽象类)
↓
StandardEnvironment (标准实现)
↓
StandardServletEnvironment (Web实现)
PropertySource优先级管理
@Component
public class PropertySourceDemo implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
// 查看所有PropertySource
System.out.println("📋 所有配置源:");
for (PropertySource<?> ps : propertySources) {
System.out.println(" - " + ps.getName() + " (" + ps.getClass().getSimpleName() + ")");
}
// 添加自定义配置源(最高优先级)
Map<String, Object> customProps = new HashMap<>();
customProps.put("custom.property", "custom value");
propertySources.addFirst(new MapPropertySource("customConfig", customProps));
// 添加到最低优先级
Map<String, Object> defaultProps = new HashMap<>();
defaultProps.put("default.property", "default value");
propertySources.addLast(new MapPropertySource("defaultConfig", defaultProps));
// 在特定位置添加
Map<String, Object> middleProps = new HashMap<>();
middleProps.put("middle.property", "middle value");
propertySources.addBefore(
"systemProperties",
new MapPropertySource("middleConfig", middleProps)
);
}
}
⚠️ 第九幕:常见坑点
坑点1:配置不生效
# ❌ 错误:缩进不对
spring:
profiles:
active: dev
# ✅ 正确:
spring:
profiles:
active: dev
坑点2:占位符循环引用
# ❌ 错误:循环引用
app:
value1: ${app.value2}
value2: ${app.value1}
# ✅ 正确:
app:
base-value: hello
value1: ${app.base-value}_world
value2: ${app.value1}_!
坑点3:Profile文件名错误
❌ 错误命名:
- application_dev.yml
- application.dev.yml
- applicationDev.yml
✅ 正确命名:
- application-dev.yml
- application-test.yml
- application-prod.yml
坑点4:环境变量命名规则
# ❌ 错误:环境变量不支持小写和点
export spring.datasource.url=jdbc:mysql://...
# ✅ 正确:使用大写和下划线
export SPRING_DATASOURCE_URL=jdbc:mysql://...
转换规则:
spring.datasource.url → SPRING_DATASOURCE_URL
server.port → SERVER_PORT
app.max-users → APP_MAX_USERS (或 APP_MAXUSERS)
🎯 第十幕:最佳实践
✅ 1. 配置分层
公共配置
↓
环境配置(dev/test/prod)
↓
本地配置(application-local.yml,不提交到Git)
.gitignore
application-local.yml
application-local.properties
✅ 2. 敏感信息外部化
# ❌ 不要把密码写在配置文件里!
spring:
datasource:
password: mySecretPassword
# ✅ 使用环境变量
spring:
datasource:
password: ${DB_PASSWORD}
✅ 3. 使用ConfigurationProperties代替@Value
// ❌ 不推荐:配置分散
@Component
public class AppConfig {
@Value("${app.name}")
private String name;
@Value("${app.version}")
private String version;
@Value("${app.timeout}")
private Long timeout;
}
// ✅ 推荐:集中管理
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private Long timeout;
// getters and setters
}
✅ 4. 配置校验
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
@NotBlank(message = "应用名称不能为空")
private String name;
@Min(value = 1, message = "超时时间至少1秒")
@Max(value = 3600, message = "超时时间最多3600秒")
private Long timeout;
@Email(message = "邮箱格式不正确")
private String adminEmail;
// getters and setters
}
🎉 总结
Environment核心功能
| 功能 | 说明 | 图标 |
|---|---|---|
| Profile管理 | 多环境配置切换 | 🌍 |
| 属性管理 | 统一管理所有配置 | ⚙️ |
| 优先级控制 | 灵活的配置覆盖 | 🎯 |
| 占位符解析 | 配置间引用 | 🔗 |
| 扩展性 | 自定义配置源 | 🔧 |
配置优先级记忆口诀
命令行最高 🎯
系统环境次之 💻
配置文件再次 📄
默认最低 ⚙️
🚀 课后作业
- 初级: 配置多环境(dev/test/prod)并切换
- 中级: 实现一个从Redis加载配置的PropertySource
- 高级: 实现配置动态刷新机制
📚 参考资料
- Spring Framework官方文档 - Environment抽象
- Spring Boot官方文档 - Externalized Configuration
- 《Spring Boot实战》
最后的彩蛋: 🎁
Spring的Environment就像一个"配置管家"🤵,他:
- 📋 知道所有配置的来源
- 🎯 知道哪个配置优先级最高
- 🌍 知道当前是什么环境
- 🔍 能帮你找到任何配置
记住这句话:
"配置虽多,Environment帮你理清头绪!" 💡
关注我,下期更精彩! 🌟
用Environment掌控所有配置! ⚙️✨
#Spring #Environment #配置管理 #最佳实践