此文是 【Spring 容器详解】-> 【ApplicationContext 做了哪些企业化的增强?】的支节点。
在Spring框架中,ApplicationContext是BeanFactory的重要扩展,提供了更丰富的功能。其中,环境配置(Environment)是ApplicationContext相对于BeanFactory的一个重要扩展功能。
1. BeanFactory的环境配置限制
1.1 BeanFactory的局限性
BeanFactory作为Spring的核心容器,主要专注于Bean的生命周期管理,在环境配置方面存在以下限制:
- 无内置环境配置能力:BeanFactory本身不提供环境配置功能
- 缺乏Profile支持:无法根据不同的环境(开发、测试、生产)加载不同的配置
- 无属性源管理:缺乏统一的属性源管理机制
- 配置解析能力有限:无法处理复杂的环境配置需求
1.2 示例代码
java
// BeanFactory缺乏环境配置能力
public class BeanFactoryEnvironmentExample {
public static void main(String[] args) {
// 创建BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// BeanFactory本身不提供环境配置功能
// 无法获取当前激活的Profile
// 无法访问环境变量和系统属性
// 无法进行条件化配置
// 需要通过其他方式处理环境配置
Properties props = new Properties();
try (InputStream inputStream = new FileInputStream("application.properties")) {
props.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. ApplicationContext的环境配置扩展
2.1 核心接口:Environment
ApplicationContext实现了Environment接口,提供了统一的环境配置能力:
java
public interface Environment extends PropertyResolver {
String[] getActiveProfiles();
String[] getDefaultProfiles();
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}
2.2 Environment的核心功能
2.2.1 Profile管理
Profile是Spring环境配置的核心概念,用于区分不同的运行环境:
java
@Component
public class ProfileExample {
@Autowired
private Environment environment;
public void demonstrateProfiles() {
// 获取当前激活的Profile
String[] activeProfiles = environment.getActiveProfiles();
System.out.println("当前激活的Profile: " + Arrays.toString(activeProfiles));
// 获取默认Profile
String[] defaultProfiles = environment.getDefaultProfiles();
System.out.println("默认Profile: " + Arrays.toString(defaultProfiles));
// 检查是否接受特定Profile
boolean isDev = environment.acceptsProfiles("dev");
boolean isProd = environment.acceptsProfiles("prod");
System.out.println("是否开发环境: " + isDev);
System.out.println("是否生产环境: " + isProd);
}
}
2.2.2 属性解析
Environment继承了PropertyResolver接口,提供了强大的属性解析能力:
java
@Component
public class PropertyResolverExample {
@Autowired
private Environment environment;
public void demonstratePropertyResolution() {
// 获取字符串属性
String appName = environment.getProperty("app.name", "默认应用名");
System.out.println("应用名称: " + appName);
// 获取数值属性
Integer port = environment.getProperty("server.port", Integer.class, 8080);
System.out.println("服务器端口: " + port);
// 获取布尔属性
Boolean debug = environment.getProperty("app.debug", Boolean.class, false);
System.out.println("调试模式: " + debug);
// 获取必需属性(不存在时抛出异常)
String requiredProperty = environment.getRequiredProperty("database.url");
System.out.println("数据库URL: " + requiredProperty);
// 解析占位符
String message = environment.resolvePlaceholders("欢迎使用 ${app.name}");
System.out.println("解析后的消息: " + message);
}
}
3. 环境配置的具体实现
3.1 属性源(PropertySource)管理
Spring通过PropertySource来管理不同来源的配置:
java
@Configuration
public class PropertySourceConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 添加多个属性源
Properties properties = new Properties();
properties.setProperty("custom.property", "自定义属性值");
configurer.setProperties(properties);
return configurer;
}
}
@Component
public class PropertySourceExample {
@Autowired
private Environment environment;
public void demonstratePropertySources() {
// 获取所有属性源
MutablePropertySources propertySources = ((ConfigurableEnvironment) environment).getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
System.out.println("属性源: " + propertySource.getName());
System.out.println("属性源类型: " + propertySource.getClass().getSimpleName());
}
// 添加自定义属性源
Properties customProps = new Properties();
customProps.setProperty("dynamic.property", "动态属性值");
PropertiesPropertySource customPropertySource =
new PropertiesPropertySource("customSource", customProps);
((ConfigurableEnvironment) environment).getPropertySources().addFirst(customPropertySource);
}
}
3.2 Profile配置
3.2.1 配置文件方式
application-dev.properties
properties
# 开发环境配置
app.name=开发环境应用
server.port=8080
database.url=jdbc:h2:mem:testdb
logging.level=DEBUG
application-prod.properties
properties
# 生产环境配置
app.name=生产环境应用
server.port=80
database.url=jdbc:mysql://prod-server:3306/proddb
logging.level=WARN
application-test.properties
properties
# 测试环境配置
app.name=测试环境应用
server.port=8081
database.url=jdbc:h2:mem:testdb
logging.level=INFO
3.2.2 代码配置方式
java
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:dev-schema.sql")
.addScript("classpath:dev-data.sql")
.build();
}
@Bean
public LoggingService loggingService() {
return new LoggingService(LogLevel.DEBUG);
}
}
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://prod-server:3306/proddb");
config.setUsername("prod_user");
config.setPassword("prod_password");
return new HikariDataSource(config);
}
@Bean
public LoggingService loggingService() {
return new LoggingService(LogLevel.WARN);
}
}
@Configuration
@Profile("test")
public class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:test-schema.sql")
.build();
}
@Bean
public LoggingService loggingService() {
return new LoggingService(LogLevel.INFO);
}
}
3.3 条件化配置
3.3.1 @Conditional注解
java
@Configuration
public class ConditionalConfig {
@Bean
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
public FeatureService featureService() {
return new FeatureService();
}
@Bean
@ConditionalOnClass(name = "com.mysql.jdbc.Driver")
public DataSource mysqlDataSource() {
// MySQL数据源配置
return new MysqlDataSource();
}
@Bean
@ConditionalOnMissingClass("com.mysql.jdbc.Driver")
public DataSource h2DataSource() {
// H2数据源配置
return new H2DataSource();
}
@Bean
@ConditionalOnExpression("'${app.environment}' == 'dev'")
public DevTools devTools() {
return new DevTools();
}
}
3.3.2 自定义条件
java
public class OnWindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = System.getProperty("os.name").toLowerCase();
return os.contains("windows");
}
}
public class OnLinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = System.getProperty("os.name").toLowerCase();
return os.contains("linux");
}
}
@Configuration
public class OsSpecificConfig {
@Bean
@Conditional(OnWindowsCondition.class)
public WindowsService windowsService() {
return new WindowsService();
}
@Bean
@Conditional(OnLinuxCondition.class)
public LinuxService linuxService() {
return new LinuxService();
}
}
4. 实际应用场景
4.1 多环境配置管理
java
@Component
public class EnvironmentManager {
@Autowired
private Environment environment;
public void printEnvironmentInfo() {
System.out.println("=== 环境信息 ===");
System.out.println("激活的Profile: " + Arrays.toString(environment.getActiveProfiles()));
System.out.println("默认Profile: " + Arrays.toString(environment.getDefaultProfiles()));
System.out.println("应用名称: " + environment.getProperty("app.name"));
System.out.println("服务器端口: " + environment.getProperty("server.port"));
System.out.println("数据库URL: " + environment.getProperty("database.url"));
}
public boolean isDevelopment() {
return environment.acceptsProfiles("dev");
}
public boolean isProduction() {
return environment.acceptsProfiles("prod");
}
public boolean isTest() {
return environment.acceptsProfiles("test");
}
}
4.2 动态配置更新
java
@Component
public class DynamicConfigManager {
@Autowired
private ConfigurableEnvironment environment;
public void updateProperty(String key, String value) {
Properties props = new Properties();
props.setProperty(key, value);
PropertiesPropertySource propertySource =
new PropertiesPropertySource("dynamicSource", props);
// 添加或更新属性源
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains("dynamicSource")) {
propertySources.replace("dynamicSource", propertySource);
} else {
propertySources.addFirst(propertySource);
}
}
public void removeProperty(String key) {
MutablePropertySources propertySources = environment.getPropertySources();
PropertySource<?> dynamicSource = propertySources.get("dynamicSource");
if (dynamicSource instanceof PropertiesPropertySource) {
Properties props = new Properties();
props.putAll(((PropertiesPropertySource) dynamicSource).getSource());
props.remove(key);
PropertiesPropertySource newSource = new PropertiesPropertySource("dynamicSource", props);
propertySources.replace("dynamicSource", newSource);
}
}
}
4.3 配置验证
java
@Component
public class ConfigurationValidator {
@Autowired
private Environment environment;
public void validateConfiguration() {
List<String> errors = new ArrayList<>();
// 验证必需属性
String[] requiredProperties = {
"app.name",
"server.port",
"database.url",
"database.username",
"database.password"
};
for (String property : requiredProperties) {
if (!environment.containsProperty(property)) {
errors.add("缺少必需属性: " + property);
}
}
// 验证属性值
try {
int port = environment.getProperty("server.port", Integer.class);
if (port <= 0 || port > 65535) {
errors.add("服务器端口必须在1-65535之间");
}
} catch (NumberFormatException e) {
errors.add("服务器端口必须是数字");
}
// 验证Profile配置
if (environment.getActiveProfiles().length == 0) {
errors.add("未指定激活的Profile");
}
if (!errors.isEmpty()) {
throw new IllegalStateException("配置验证失败:\n" + String.join("\n", errors));
}
}
}
5. 高级特性
5.1 自定义PropertySource
java
public class DatabasePropertySource extends PropertySource<Map<String, Object>> {
private final Map<String, Object> properties = new HashMap<>();
public DatabasePropertySource(String name) {
super(name);
loadPropertiesFromDatabase();
}
private void loadPropertiesFromDatabase() {
// 从数据库加载配置
// 这里只是示例,实际实现需要连接数据库
properties.put("db.config.key1", "value1");
properties.put("db.config.key2", "value2");
}
@Override
public Object getProperty(String name) {
return properties.get(name);
}
@Override
public boolean containsProperty(String name) {
return properties.containsKey(name);
}
}
@Configuration
public class CustomPropertySourceConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 添加自定义属性源
DatabasePropertySource dbPropertySource = new DatabasePropertySource("databaseSource");
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(dbPropertySource);
configurer.setPropertySources(propertySources);
return configurer;
}
}
5.2 配置加密
java
@Component
public class EncryptedPropertyResolver {
private final Environment environment;
private final String encryptionKey;
public EncryptedPropertyResolver(Environment environment) {
this.environment = environment;
this.encryptionKey = environment.getProperty("app.encryption.key", "defaultKey");
}
public String getDecryptedProperty(String key) {
String encryptedValue = environment.getProperty(key);
if (encryptedValue != null && encryptedValue.startsWith("ENC(") && encryptedValue.endsWith(")")) {
String encrypted = encryptedValue.substring(4, encryptedValue.length() - 1);
return decrypt(encrypted);
}
return encryptedValue;
}
private String decrypt(String encrypted) {
// 简单的解密示例,实际应用中应使用更安全的加密算法
try {
// 这里使用Base64解码作为示例
byte[] decoded = Base64.getDecoder().decode(encrypted);
return new String(decoded, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失败", e);
}
}
}
5.3 配置热重载
java
@Component
public class ConfigReloader {
@Autowired
private ConfigurableEnvironment environment;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@PostConstruct
public void startReloadScheduler() {
scheduler.scheduleAtFixedRate(this::reloadConfig, 30, 30, TimeUnit.SECONDS);
}
public void reloadConfig() {
try {
// 重新加载配置文件
Properties props = new Properties();
Resource resource = new ClassPathResource("application.properties");
if (resource.exists()) {
try (InputStream inputStream = resource.getInputStream()) {
props.load(inputStream);
}
PropertiesPropertySource propertySource =
new PropertiesPropertySource("reloadedSource", props);
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains("reloadedSource")) {
propertySources.replace("reloadedSource", propertySource);
} else {
propertySources.addFirst(propertySource);
}
System.out.println("配置已重新加载");
}
} catch (IOException e) {
System.err.println("重新加载配置失败: " + e.getMessage());
}
}
@PreDestroy
public void shutdown() {
scheduler.shutdown();
}
}
6. 最佳实践
6.1 配置分层
java
@Configuration
@PropertySource({
"classpath:application.properties",
"classpath:application-${spring.profiles.active}.properties"
})
public class LayeredConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 设置属性源优先级
configurer.setLocalOverride(true);
configurer.setIgnoreUnresolvablePlaceholders(false);
return configurer;
}
}
6.2 配置验证
java
@Component
public class ConfigurationHealthCheck {
@Autowired
private Environment environment;
public Health checkConfiguration() {
Health.Builder builder = Health.up();
// 检查关键配置
String[] criticalProperties = {
"database.url",
"redis.host",
"app.secret"
};
for (String property : criticalProperties) {
if (!environment.containsProperty(property)) {
builder.down().withDetail("missing_property", property);
}
}
// 检查Profile配置
if (environment.getActiveProfiles().length == 0) {
builder.down().withDetail("missing_profile", "No active profile");
}
return builder.build();
}
}
6.3 配置文档生成
java
@Component
public class ConfigurationDocumentation {
@Autowired
private Environment environment;
public void generateDocumentation() {
System.out.println("=== 配置文档 ===");
MutablePropertySources propertySources =
((ConfigurableEnvironment) environment).getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
System.out.println("\n属性源: " + propertySource.getName());
if (propertySource instanceof PropertiesPropertySource) {
Properties props = ((PropertiesPropertySource) propertySource).getSource();
for (String key : props.stringPropertyNames()) {
String value = props.getProperty(key);
// 隐藏敏感信息
if (key.contains("password") || key.contains("secret")) {
value = "***";
}
System.out.println(" " + key + " = " + value);
}
}
}
}
}
总结
ApplicationContext在环境配置方面相比BeanFactory提供了以下重要扩展:
- 统一的配置管理:通过Environment接口提供统一的配置访问能力
- Profile支持:支持多环境配置,可以根据不同环境加载不同配置
- 属性源管理:支持多种配置源,包括文件、环境变量、系统属性等
- 条件化配置:支持基于条件的Bean配置
- 动态配置更新:支持运行时动态更新配置