Appearance
initPropertySources方法扩展点
一、initPropertySources的定位:容器启动流程中的关键步骤
Spring容器的核心启动方法是ConfigurableApplicationContext#refresh(),它定义了容器初始化的完整流程。initPropertySources是refresh()中的早期步骤,位于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的作用:初始化属性源与验证配置
initPropertySources是AbstractApplicationContext中的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. Environment与PropertySource的关系
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的构造方法会调用父类AbstractEnvironment的customizePropertySources,添加默认的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.url、redis.host),确保容器启动时这些配置存在,避免运行时出现NullPointerException或连接失败。
六、initPropertySources与其他扩展点的区别
| 扩展点 | 时机 | 作用 |
|---|---|---|
initPropertySources | refresh()早期(Bean定义前) | 初始化属性源、验证必要配置 |
BeanFactoryPostProcessor | refresh()中期(Bean定义后、实例化前) | 修改Bean定义(如替换占位符、修改属性) |
ApplicationContextInitializer | refresh()前(Spring Boot扩展) | 初始化ApplicationContext(如修改环境) |
总结
initPropertySources是Spring容器启动过程中的早期扩展点,核心作用是初始化属性源和验证必要配置。通过重写该方法,我们可以灵活地扩展Spring的配置机制,满足自定义配置源(如数据库、Redis、远程配置中心)的需求,确保配置的正确性和灵活性。
其关键优势在于:
- 时机早:在Bean定义加载前调用,确保配置能被后续步骤正确解析;
- 优先级高:通过
addFirst()可以覆盖默认配置; - 验证严格:通过
setRequiredProperties()确保必要配置存在,避免运行时错误。
掌握initPropertySources的扩展方式,能帮助我们更好地理解Spring的配置机制,并实现更灵活的配置管理。
