Spring 源码阅读 03:初始化 Spring 上下文信息
在之前的 ApplicationContext 初始化 Spring 容器 一文中,我们提到了 AbstractApplicationContext#refresh
方法是一个非常重要的方法,它包含了 Spring 容器初始化的整个流程,我们将对这里的源码进行细致地阅读。本篇要阅读的是初始化上下文信息的部分,这是 refresh 方法的第一步,也就是源码中的这一个方法调用:
// Prepare this context for refreshing.
// 初始化上下文的信息
prepareRefresh();
我们查看这个方法的源码:
/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
// === 第一部分 ===
// Switch to active.
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this);
}
else {
logger.debug("Refreshing " + getDisplayName());
}
}
// === 第二部分 ===
// Initialize any placeholder property sources in the context environment.
// 在上下文环境中初始化参数占位符
initPropertySources();
// === 第三部分 ===
// Validate that all properties marked as required are resolvable:
// see ConfigurablePropertyResolver#setRequiredProperties
// 验证必要的参数
getEnvironment().validateRequiredProperties();
// === 第四部分 ===
// Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
// Reset local application listeners to pre-refresh state.
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
为了方便分析,我用注释将方法体分成了四个部分,并且在关键部分加上了中文注释。
在方法中,除了配置一些最基本的初始化操作(比如设置各种状态、启动时间等),比较重要的是第二、三部分的两个方法调用。我们分开来说
初始化参数占位符
先看 initPropertySources();
这个方法调用。
/**
* <p>Replace any stub property sources with actual instances.
* @see org.springframework.core.env.PropertySource.StubPropertySource
* @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources
*/
protected void initPropertySources() {
// For subclasses: do nothing by default.
}
这个方法中并没有任何实现逻辑,而且是 protected 修饰的,因此,这里是一个扩展点,子类可以通过重写这个方法来使用实例替换 XML 文件中的 ${}
占位符,这里只是给占位符提供一个实例作为替换内容,并未进行替换的操作,实际的替换操作在后续的流程中。
比如说,在一个子类中,重写了 initPropertySources
方法,并添加了一个名叫 spring.datasource.driver-class-name
的参数的值为 com.mysql.cj.jdbc.Driver
,那么,如果在 XML 文件中出现了 ${spring.datasource.driver-class-name}
,Spring 就会将其替换为 com.mysql.cj.jdbc.Driver
。
验证必要的参数
接下来再看 getEnvironment().validateRequiredProperties();
。
先简单看一下 getEnvironment()
,通过这个方法,可以获取到当前 Spring 上下文的运行时环境。
/**
* Return the { @code Environment} for this application context in configurable
* form, allowing for further customization.
* <p>If none specified, a default environment will be initialized via
* { @link #createEnvironment()}.
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
/**
* Create and return a new { @link StandardEnvironment}.
* <p>Subclasses may override this method in order to supply
* a custom { @link ConfigurableEnvironment} implementation.
*/
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
从源码中可以看出,当前的运行时环境被保存在 AbstractApplicationContext
的 environment
成员变量中,当我们需要获取当前运行时环境对象的时候,则会读取这个成员变量的值,如果为空,则创建一个 StandardEnvironment
类型的对象。
说回到 validateRequiredProperties()
方法的调用,这个方法的实现在 StandardEnvironment
的父类 AbstractEnvironment
中:
@Override
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
this.propertyResolver.validateRequiredProperties();
}
这里,将任务委托给了成员变量 propertyResolver
,它是 ConfigurablePropertyResolver
类型的变量,在创建 StandardEnvironment
实例的时候就已经被初始化好了。
propertyResolver
的 validateRequiredProperties
方法实现可以在 AbstractPropertyResolver
类中找到:
@Override
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
这里主要是通过遍历 requiredProperties
成员变量中的参数名,查看是否都有对应的值,如果有参数没有对应的值,则会报错。在 propertyResolver
被创建的时候,requiredProperties
会被初始化为一个空集合,我们可以在代码中通过调用它的 setRequiredProperties
方法,向 requiredProperties
参数名。当 Spring 容器初始化的时候,如果集合中存在找不到参数值的参数,则会报错。我们也可以利用这个特性来做容器初始化的必要参数检查。
下一篇我们来分析 BeanFactory 的初始化。