Skip to content

initPropertySources方法扩展点

一、initPropertySources的定位:容器启动流程中的关键步骤

Spring容器的核心启动方法是ConfigurableApplicationContext#refresh(),它定义了容器初始化的完整流程。initPropertySourcesrefresh()中的早期步骤,位于prepareRefresh()方法内,具体流程如下:

mermaid
graph TD
    A[refresh()] --> B[prepareRefresh()]
    B --> C[initPropertySources()]  // 扩展点:初始化属性源
    B --> D[getEnvironment().validateRequiredProperties()]  // 验证必要属性
    A --> E[obtainFreshBeanFactory()]  // 获取BeanFactory
    A --> F[loadBeanDefinitions()]  // 加载Bean定义(依赖环境属性)

关键时机
initPropertySources环境对象(Environment)创建之后Bean定义加载之前调用。此时环境已初始化(默认包含系统属性systemProperties、环境变量systemEnvironment),但Bean定义尚未解析,因此适合添加自定义属性源验证必要配置,确保后续Bean定义中的占位符(如${my.app.name})能正确解析。

二、initPropertySources的作用:初始化属性源与验证配置

initPropertySourcesAbstractApplicationContext中的protected空方法,留给子类重写以实现扩展。其核心作用有两点:

1. 初始化属性源(添加自定义PropertySource

PropertySource是Spring抽象的配置源(如系统属性、环境变量、配置文件、数据库等),Environment通过MutablePropertySources管理多个PropertySource的优先级(前面的PropertySource优先级更高)。
通过initPropertySources,我们可以向Environment中添加自定义PropertySource,例如:

  • 从数据库/Redis加载配置;
  • 从远程配置中心(Nacos、Apollo)加载配置;
  • 添加动态生成的配置。

2. 验证必要属性

通过Environment#setRequiredProperties()设置必要属性键,Spring会自动验证这些属性是否存在于Environment中。若缺失,会抛出MissingRequiredPropertiesException阻止容器启动,避免运行时错误。

三、源码剖析:从refresh()initPropertySources

我们从AbstractApplicationContext的源码入手,逐步拆解initPropertySources的调用链。

1. refresh()方法:容器启动的模板流程

AbstractApplicationContext#refresh()是模板方法,定义了容器启动的核心步骤:

java
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新:初始化状态、调用initPropertySources、验证必要属性
        prepareRefresh();
        
        // 2. 获取新鲜的BeanFactory(销毁旧的,创建新的)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3. 准备BeanFactory:配置类加载器、后置处理器等
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 后置处理BeanFactory(子类扩展)
            postProcessBeanFactory(beanFactory);
            
            // 5. 调用BeanFactory后置处理器(修改Bean定义)
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 6. 注册Bean后置处理器(修改Bean实例)
            registerBeanPostProcessors(beanFactory);
            
            // 7. 初始化消息源(国际化)
            initMessageSource();
            
            // 8. 初始化事件多播器
            initApplicationEventMulticaster();
            
            // 9. 子类扩展:初始化特殊Bean(如Tomcat容器)
            onRefresh();
            
            // 10. 注册事件监听器
            registerListeners();
            
            // 11. 完成BeanFactory初始化:实例化所有非懒加载Bean
            finishBeanFactoryInitialization(beanFactory);
            
            // 12. 完成刷新:发布事件、启动生命周期处理器
            finishRefresh();
        } catch (BeansException ex) {
            // 异常处理:销毁BeanFactory、取消刷新
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        }
    }
}

2. prepareRefresh():初始化前的准备工作

initPropertySources位于prepareRefresh()方法内,是容器刷新的第一步

java
protected void prepareRefresh() {
    // 记录启动时间、设置状态(active=true, closed=false)
    this.startupDate = System.currentTimeMillis();
    this.closed.set(false);
    this.active.set(true);
    
    // 日志输出:启动信息
    if (logger.isDebugEnabled()) {
        logger.debug("Refreshing " + this);
    }
    
    // 【扩展点】初始化属性源(子类重写)
    initPropertySources();
    
    // 【关键】验证必要属性(基于Environment中的requiredProperties)
    getEnvironment().validateRequiredProperties();
    
    // 初始化早期事件监听器(用于记录启动过程中的事件)
    this.earlyApplicationEvents = new LinkedHashSet<>();
}

3. initPropertySources():默认空实现

AbstractApplicationContext中的initPropertySources空方法,留给子类重写:

java
protected void initPropertySources() {
    // 默认无操作,子类可扩展
}

4. EnvironmentPropertySource的关系

getEnvironment()返回ConfigurableEnvironment(默认实现为StandardEnvironment),它通过MutablePropertySources管理多个PropertySource

java
@Override
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        this.environment = createEnvironment(); // 默认创建StandardEnvironment
    }
    return this.environment;
}

protected ConfigurableEnvironment createEnvironment() {
    return new StandardEnvironment();
}

StandardEnvironment的构造方法会调用父类AbstractEnvironmentcustomizePropertySources,添加默认的PropertySource

java
// AbstractEnvironment构造方法
public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}

// StandardEnvironment重写customizePropertySources,添加系统属性和环境变量
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new MapPropertySource(
        SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    propertySources.addLast(new SystemEnvironmentPropertySource(
        SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

默认的PropertySource顺序为:systemProperties(后面)→ systemEnvironment(更后面)。通过initPropertySources添加的PropertySource可以调整优先级(如addFirst()放在最前面,覆盖默认配置)。

四、扩展initPropertySources的具体案例

我们通过自定义ApplicationContext重写initPropertySources,实现从Redis加载配置验证必要属性的功能。

1. 实现自定义PropertySource(从Redis加载配置)

PropertySource是抽象类,需要实现getProperty()(获取属性)和getPropertyNames()(获取所有属性键)方法:

java
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Set;

/**
 * 从Redis加载配置的PropertySource
 */
public class RedisPropertySource extends EnumerablePropertySource<RedisTemplate<String, String>> {
    // PropertySource名称(唯一标识)
    private static final String NAME = "redisPropertySource";
    // Redis模板(用于操作Redis)
    private final RedisTemplate<String, String> redisTemplate;
    // 需要加载的Redis键集合
    private final Set<String> keys;

    public RedisPropertySource(RedisTemplate<String, String> redisTemplate, Set<String> keys) {
        super(NAME, redisTemplate);
        this.redisTemplate = redisTemplate;
        this.keys = keys;
    }

    /**
     * 根据属性键从Redis获取值
     */
    @Override
    public Object getProperty(String name) {
        if (keys.contains(name)) {
            return redisTemplate.opsForValue().get(name);
        }
        return null;
    }

    /**
     * 获取所有属性键(用于EnumerablePropertySource)
     */
    @Override
    public String[] getPropertyNames() {
        return keys.toArray(new String[0]);
    }
}

2. 自定义ApplicationContext重写initPropertySources

通过重写initPropertySources,向Environment中添加RedisPropertySource并验证必要属性:

java
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.HashSet;
import java.util.Set;

/**
 * 自定义ApplicationContext,从Redis加载配置
 */
public class RedisConfigApplicationContext extends AnnotationConfigApplicationContext {
    // Redis模板(通过构造方法注入)
    private final RedisTemplate<String, String> redisTemplate;
    // 需要加载的Redis键(例如:my.app.name、my.app.version)
    private final Set<String> redisKeys = new HashSet<>();

    public RedisConfigApplicationContext(RedisTemplate<String, String> redisTemplate, Class<?>... componentClasses) {
        this.redisTemplate = redisTemplate;
        // 添加需要加载的Redis键
        redisKeys.add("my.app.name");
        redisKeys.add("my.app.version");
        // 注册组件类(如@Configuration、@Component)
        register(componentClasses);
        // 启动容器(调用refresh())
        refresh();
    }

    @Override
    protected void initPropertySources() {
        super.initPropertySources(); // 调用父类空实现(可选)
        
        // 1. 获取环境对象(ConfigurableEnvironment)
        ConfigurableEnvironment env = getEnvironment();
        
        // 2. 添加RedisPropertySource到环境的PropertySources(放在最前面,优先级最高)
        env.getPropertySources().addFirst(new RedisPropertySource(redisTemplate, redisKeys));
        
        // 3. 设置必要属性(必须存在于Environment中,否则启动失败)
        env.setRequiredProperties("my.app.name"); // 验证"my.app.name"是否存在
    }
}

3. 使用自定义ApplicationContext

java
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 测试类:使用RedisConfigApplicationContext启动容器
 */
public class RedisConfigTest {
    public static void main(String[] args) {
        // 1. 配置RedisTemplate(实际项目中可通过Spring配置)
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        RedisConnectionFactory connectionFactory = ...; // 初始化Redis连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();

        // 2. 向Redis中添加测试数据(模拟配置)
        redisTemplate.opsForValue().set("my.app.name", "RedisConfigApp");
        redisTemplate.opsForValue().set("my.app.version", "1.0.0");

        // 3. 创建自定义ApplicationContext(加载Redis配置)
        RedisConfigApplicationContext context = new RedisConfigApplicationContext(
                redisTemplate,
                MyBean.class // 注册需要的组件
        );

        // 4. 获取Bean并打印属性(验证配置是否生效)
        MyBean myBean = context.getBean(MyBean.class);
        System.out.println("App Name: " + myBean.getAppName()); // 输出:RedisConfigApp
        System.out.println("App Version: " + myBean.getAppVersion()); // 输出:1.0.0
    }

    /**
     * 测试Bean:使用@Value注入Redis中的配置
     */
    @Component
    public static class MyBean {
        @Value("${my.app.name}")
        private String appName;

        @Value("${my.app.version}")
        private String appVersion;

        //  getter/setter
    }
}

五、initPropertySources的扩展场景

initPropertySources的扩展场景主要围绕配置源的自定义配置验证,常见场景包括:

1. 从远程配置中心加载配置

例如,从Nacos、Apollo等配置中心加载动态配置,通过initPropertySources添加对应的PropertySource,实现配置的集中管理和动态刷新(需结合配置中心的监听机制)。

2. 从数据库/Redis加载配置

对于需要持久化或动态修改的配置(如系统参数、业务开关),可以从数据库或Redis加载,避免硬编码到配置文件中。

3. 覆盖默认配置

通过addFirst()将自定义PropertySource放在最前面,覆盖系统属性、环境变量或配置文件中的同名属性,实现灵活的配置覆盖(如测试环境覆盖生产环境配置)。

4. 验证必要配置

通过env.setRequiredProperties()设置必要属性(如database.urlredis.host),确保容器启动时这些配置存在,避免运行时出现NullPointerException或连接失败。

六、initPropertySources与其他扩展点的区别

扩展点时机作用
initPropertySourcesrefresh()早期(Bean定义前)初始化属性源、验证必要配置
BeanFactoryPostProcessorrefresh()中期(Bean定义后、实例化前)修改Bean定义(如替换占位符、修改属性)
ApplicationContextInitializerrefresh()前(Spring Boot扩展)初始化ApplicationContext(如修改环境)

总结

initPropertySources是Spring容器启动过程中的早期扩展点,核心作用是初始化属性源验证必要配置。通过重写该方法,我们可以灵活地扩展Spring的配置机制,满足自定义配置源(如数据库、Redis、远程配置中心)的需求,确保配置的正确性和灵活性。

其关键优势在于:

  • 时机早:在Bean定义加载前调用,确保配置能被后续步骤正确解析;
  • 优先级高:通过addFirst()可以覆盖默认配置;
  • 验证严格:通过setRequiredProperties()确保必要配置存在,避免运行时错误。

掌握initPropertySources的扩展方式,能帮助我们更好地理解Spring的配置机制,并实现更灵活的配置管理。