【附录】Spring 环境配置 基础及应用

此文是 【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提供了以下重要扩展:

  1. 统一的配置管理:通过Environment接口提供统一的配置访问能力
  2. Profile支持:支持多环境配置,可以根据不同环境加载不同配置
  3. 属性源管理:支持多种配置源,包括文件、环境变量、系统属性等
  4. 条件化配置:支持基于条件的Bean配置
  5. 动态配置更新:支持运行时动态更新配置
相关推荐
用户833810251225 分钟前
我为什么做PmMock:让接口设计不再头疼
前端·后端
二闹13 分钟前
IService 和 BaseMapper:CRUD 操作的选择指南
后端
dylan_QAQ13 分钟前
【附录】Spring AOP 基础知识及应用
后端·spring
Java中文社群25 分钟前
抱歉!Java面试标准答案最不重要
java·后端·面试
jiguanghover36 分钟前
n8n 创建多维表格犯的错误
前端·后端
dylan_QAQ37 分钟前
【附录】Spring 配置属性绑定 基础及应用
后端·spring
泡海椒37 分钟前
jquick Path:让JSON数据处理像呼吸一样简单
后端
鹿鹿的布丁40 分钟前
freeswitch+freeswitch+语音网关拨打电话
后端
dylan_QAQ42 分钟前
【附录】Spring 缓存支持 基础及应用
后端·spring
笑衬人心。2 小时前
缓存的三大问题分析与解决
java·spring·缓存