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();
}

从源码中可以看出,当前的运行时环境被保存在 AbstractApplicationContextenvironment 成员变量中,当我们需要获取当前运行时环境对象的时候,则会读取这个成员变量的值,如果为空,则创建一个 StandardEnvironment 类型的对象。

说回到 validateRequiredProperties() 方法的调用,这个方法的实现在 StandardEnvironment 的父类 AbstractEnvironment 中:

@Override
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
   this.propertyResolver.validateRequiredProperties();
}

这里,将任务委托给了成员变量 propertyResolver,它是 ConfigurablePropertyResolver 类型的变量,在创建 StandardEnvironment 实例的时候就已经被初始化好了。

propertyResolvervalidateRequiredProperties 方法实现可以在 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 的初始化。