Spring IOC原始碼分析之-重新整理前的準備工作

c旋兒發表於2019-06-01

ClassPathXmlApplicationContext的註冊方式

原始碼分析基於Spring4.3

ClassPathXmlApplicationContext入口,最終都會呼叫到

/*
     * 使用給定父級建立新的ClassPathXmlApplicationContext,從給定的XML檔案載入定義資訊。
     * 載入所有的bean 定義資訊並且建立所有的單例
     * 或者,在進一步配置上下文之後手動呼叫重新整理。
*/
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
  throws BeansException {

  super(parent);
  setConfigLocations(configLocations);
  if (refresh) {
    refresh();
  }
}

上述註釋的解釋如是說:在容器的啟動過程中,初始化過程中所有的bean都是單例存在的

自動重新整理

ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");

就等同於

手動重新整理

ApplicationContext context = new ClassPathXmlApplicationContext();
context.register("xxx.xml");
context.refresh();

上述一共有三條鏈路,下面來一一分析

載入父子容器

  • 首先是載入並初始化父容器的方法

Spring IOC原始碼分析之-重新整理前的準備工作

  1. 第一個出場的是ClassPathXmlApplicationContext,它是一個獨立的應用程式上下文,從類路徑獲取上下文定義檔案,能夠將普通路徑解析為包含包路徑的類路徑資源名稱。它可以支援Ant-Style(路徑匹配原則),它是一站式應用程式的上下文,考慮使用GenericApplicationContext類結合XmlBeanDefinitionReader來設定更靈活的上下文配置。

Ant-Style 路徑匹配原則,例如 "mypackages/application-context.xml" 可以用"mypackages/*-context.xml" 來替換。

⚠️注意: 如果有多個上下文配置,那麼之後的bean定義將覆蓋之前載入的檔案。這可以用來通過額外的XML檔案故意覆蓋某些bean定義

  1. 隨後不緊不慢走過來的不是一個完整的somebody,AbstractXmlApplicationContext, 它是為了方便ApplicationContext的實現而出現的(抽象類一個很重要的思想就是適配)。AbstractXmlApplicationContext 的最主要作用就是通過建立一個XML閱讀器解析ClassPathXmlApplicationContext 註冊的配置檔案。它有兩個最主要的方法 loadBeanDefinitions(DefaultListableBeanFactory beanFactory)loadBeanDefinitions(XmlBeanDefinitionReader reader)

  2. 下一個緩緩出場的是 AbstractRefreshableConfigApplicationContext ,它就像是中間人的角色,並不作多少工作,很像古代丞相的奏摺要呈遞給皇上,它的作用就相當於是拿奏摺的角色。它用作XML應用程式上下文實現的基類,例如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext和XmlWebApplicationContext
  3. 當老闆的一般都比較聽小祕的,那麼AbstractRefreshableApplicationContext就扮演了小祕的角色,它是ApplicationContext的基類,支援多次呼叫refresh()方法,每次都會建立一個新的內部bean factory例項。繼承 AbstractRefreshableApplicationContext 需要唯一實現的方法就是loadBeanDefinitions,在每一次呼叫重新整理方法的時候。一個具體的實現是載入bean定義資訊的DefaultListableBeanFactory
  4. 但是隻有小祕給老闆遞交請辭不行,中間還要有技術leader 來縱覽大局,向上與老闆探討公司發展計劃,在下領導新人做專案打硬仗(這種男人真的很有魅力哈哈哈),但是技術leader也不能幹完所有的工作,他還需要交給手下的程式設計師去幫他完成具體的工作,程式設計師接到一項工作,看看有沒有可複用的專案和開源類庫,發現有可用的,直接把"引用"連結過去就可以了。這就是容器的初始化工作,但是這一步的流程還沒有結束,你還得時刻記住你是給boss幹活的。

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
  // 交給其他程式設計師去完成的工作
  this();
  // 明確自己的老闆是誰
  setParent(parent);
}

public AbstractApplicationContext() {
  this.resourcePatternResolver = getResourcePatternResolver();
}

// 返回 ResourcePatternResolver 去解析資源例項中的匹配模式,預設的是 PathMatchingResourcePatternResolver 支援 Ant-Style 模式。
protected ResourcePatternResolver getResourcePatternResolver() {
  return new PathMatchingResourcePatternResolver(this);
}

// 此時的resourceLoader 就是ClassPathXmlApplicationContext 物件。
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
  Assert.notNull(resourceLoader, "ResourceLoader must not be null");
  this.resourceLoader = resourceLoader;
}

你需要一些程式設計師幫你做具體的編碼工作,也需要明確你是公司的員工,需要聽從老闆的,所以你需要明確老闆是誰

@Override
public void setParent(@Nullable ApplicationContext parent) {
  this.parent = parent;
  if (parent != null) {
    Environment parentEnvironment = parent.getEnvironment();
    if (parentEnvironment instanceof ConfigurableEnvironment) {
      getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
    }
  }
}

但是這個時候老闆出差了,不在了(因為傳過來的parent 是 null),所以你需要自己做一些decision。至此,第一條線路就分析完成了。

配置路徑解析

  • 第二條線路,ApplicationContext中的 setConfigLocations(configLocations)
// 引數傳過來的是可變引數,可變引數是一個陣列,也就是說,你可以傳遞多個配置檔案,用","分隔起來。
public void setConfigLocations(@Nullable String... locations) {
  if (locations != null) {
    Assert.noNullElements(locations, "Config locations must not be null");
    // configlocations 是一個可為空的String陣列,可以為null,為null可以進行手動註冊。
    this.configLocations = new String[locations.length];
    // 解析陣列中的每一個配置檔案的路徑。
    for (int i = 0; i < locations.length; i++) {
      this.configLocations[i] = resolvePath(locations[i]).trim();
    }
  }
  // 預設是直接建立了一個 ClassPathXmlApplicationContext 的無引數的建構函式,採用手動註冊的方式。
  else {
    this.configLocations = null;
  }
}

關鍵點:路徑解析方法 : AbstractRefreshableConfigApplicationContext 中的 resolvePath(locations[i]).trim(); 來看看是如何進行路徑解析的

// 解析給定的路徑,必要時用相應的環境屬性值替換佔位符。應用於路徑配置。
protected String resolvePath(String path) {
  return getEnvironment().resolveRequiredPlaceholders(path);
}

涉及兩個方法,AbstractRefreshableConfigApplicationContext 中的getEnvironment() 和 validateRequiredProperties(),先來看第一個

  • getEnvironment()

    // 以配置的形式返回此應用程式上下文的Environment,來進一步自定義
    // 如果沒有指定,則通過初始化預設的環境。
    @Override
    public ConfigurableEnvironment getEnvironment() {
      if (this.environment == null) {
        // 使用預設的環境配置
        this.environment = createEnvironment();
      }
      return this.environment;
    }
    • 下面來看一下createEnvironment()如何初始化預設的環境:

      // 建立並返回一個 StandardEnvironment,子類重寫這個方法為了提供
      // 一個自定義的 ConfigurableEnvironment 實現。
      protected ConfigurableEnvironment createEnvironment() {
              // StandardEnvironment 繼承AbstractEnvironment,而AbstractEnvironment
              // 實現了ConfigurableEnvironment
              return new StandardEnvironment();
          }
      

      其實很簡單,也只是new 了一個StandardEnvironment() 的構造器而已。StandardEnvironment是什麼?非web應用程式的Environment 的標準實現。他實現了AbstractEnvironment 抽象類,下面是具體的繼承樹:

Spring IOC原始碼分析之-重新整理前的準備工作

StandardEnvironment是AbstractEnvironment的具體實現,而AbstractEnvironment又是繼承了ConfigurableEnvironment介面,提供了某些方法的具體實現,ConnfigurableEnvironment 繼承了Environment,而Environment 和 ConfigurablePropertyResolver 同時繼承了PropertyResolver

下面來看一下StandardEnvironment() 的原始碼:

public class StandardEnvironment extends AbstractEnvironment {

  // 系統屬性資源名稱
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

  // JVM系統屬性資源名:
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

  //為標準的Java 環境 自定義合適的屬性檔案
    @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()));
    }

}


現在讀者就會產生疑問,不是說new出來一個標準的StandardEnvironment 實現嗎,但是StandardEnvironment並沒有預設的構造方法啊?這是什麼回事呢?

其實StandardEnvironment 的構造方法是 AbstractEnvironment:

public AbstractEnvironment() {
  // 實現自定義屬性資源的方法,也就是StandardEnvironment中customizePropertySources()
  customizePropertySources(this.propertySources);
  if (logger.isDebugEnabled()) {
    logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
  }
}


上述的`customizePropertySources` 由`StandardEnvironment` 來實現,具體如下

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


由於容器在剛起步的時候 propertySources 是null,所以新增完系統環境(systemEnvironment)和系統屬性(systemProperties) 之後,會變成下圖所示
    • Spring IOC原始碼分析之-重新整理前的準備工作

      如何獲取系統屬性和如何獲取系統環境沒有往下跟,有興趣的讀者可以繼續沿用。

      大致截一個圖,裡面大概的屬性是這樣

      systemProperties

      Spring IOC原始碼分析之-重新整理前的準備工作

      systemEnvironment

      Spring IOC原始碼分析之-重新整理前的準備工作

  • 另外一個是 resolveRequiredPlaceholders,它是由 PropertyResolver 超頂級介面定義的方法
// 在給定的text 引數中解析${} 佔位符,將其替換為getProperty 解析的相應屬性值。
// 沒有預設值的無法解析的佔位符將導致丟擲IllegalArgumentException。
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
    • AbstractPropertyResolver 子類來實現,且看AbstractPropertyResolver 的繼承樹

    -Spring IOC原始碼分析之-重新整理前的準備工作

    具體實現的方法如下:

    // 傳遞進來的文字就是解析過的 配置檔案 SimpleName
    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
      if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
      }
      return doResolvePlaceholders(text, this.strictHelper);
    }
    
    // 呼叫createPlaceholderHelper
    private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
            return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
                    this.valueSeparator, ignoreUnresolvablePlaceholders);
    }
    
    ----------------------------PropertyPlaceholderHelper-------------------------------
    
      // PropertyPlaceholderHelper載入的時候會把下面的特殊字元放進去
      static {
            wellKnownSimplePrefixes.put("}", "{");
            wellKnownSimplePrefixes.put("]", "[");
            wellKnownSimplePrefixes.put(")", "(");
        }
    
    /*
        建立一個新的 PropertyPlaceholderHelper 使用提供的字首 和 字尾
         * 引數解釋:placeholderPrefix 佔位符開頭的字首
         *         placeholderSuffix 佔位符結尾的字尾
         *         valueSeparator 佔位符變數和關聯的預設值 之間的分隔符
         *         ignoreUnresolvablePlaceholders 指示是否應忽略不可解析的佔位符。
    */
    
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                                     @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
    
      Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
      Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
      this.placeholderPrefix = placeholderPrefix;
      this.placeholderSuffix = placeholderSuffix;
      String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
      if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
        this.simplePrefix = simplePrefixForSuffix;
      }
      else {
        this.simplePrefix = this.placeholderPrefix;
      }
      this.valueSeparator = valueSeparator;
      this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }
    
    

    解析完成佔位符之後,需要做真正的解析,呼叫AbstractPropertyResolver中的doResolvePlaceholders 方法。

    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
      return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
          return getPropertyAsRawString(placeholderName);
        }
      });
    }
    
    

    PlaceholderResolver 是 PropertyPlaceholderHelper類的內部類,這是一種匿名內部類的寫法,它真正呼叫的就是 PropertyPlaceholderHelper中的 replacePlaceholders 方法,具體如下:

    // 將格式為 ${name} 的佔位符替換為從提供 PlaceholderResolver 返回的值。
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
      Assert.notNull(value, "'value' must not be null");
      return parseStringValue(value, placeholderResolver, new HashSet<String>());
    }
    
    
    protected String parseStringValue(
                String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
    
            StringBuilder result = new StringBuilder(value);
    
            int startIndex = value.indexOf(this.placeholderPrefix);
    
        // 判斷指定的佔位符有無 ${ 存在,沒有的話直接返回
            while (startIndex != -1) {
                int endIndex = findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) {
                    String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                    String originalPlaceholder = placeholder;
                    if (!visitedPlaceholders.add(originalPlaceholder)) {
                        throw new IllegalArgumentException(
                                "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                    }
                    // Recursive invocation, parsing placeholders contained in the placeholder key.
                    placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                    // Now obtain the value for the fully resolved key...
                    String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                    if (propVal == null && this.valueSeparator != null) {
                        int separatorIndex = placeholder.indexOf(this.valueSeparator);
                        if (separatorIndex != -1) {
                            String actualPlaceholder = placeholder.substring(0, separatorIndex);
                            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                            propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                            if (propVal == null) {
                                propVal = defaultValue;
                            }
                        }
                    }
                    if (propVal != null) {
                        // Recursive invocation, parsing placeholders contained in the
                        // previously resolved placeholder value.
                        propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                        result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Resolved placeholder '" + placeholder + "'");
                        }
                        startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                    }
                    else if (this.ignoreUnresolvablePlaceholders) {
                        // Proceed with unprocessed value.
                        startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                    }
                    else {
                        throw new IllegalArgumentException("Could not resolve placeholder '" +
                                placeholder + "'" + " in value \"" + value + "\"");
                    }
                    visitedPlaceholders.remove(originalPlaceholder);
                }
                else {
                    startIndex = -1;
                }
            }
    
            return result.toString();
        }
    

    直白一點,上述過程就是用來判斷有沒有 ${ 這個佔位符,如果有的話就進入下面的判斷邏輯,把${}

    中的值替換為 PlaceholderResolver 返回的值,如果沒有的話,就直接返回。

容器重新整理

​ 在經過上述的準備工作完成後,接下來就是整個IOC,DI和AOP的核心步驟了,也是Spring框架的靈魂。由於原始碼太多,設計範圍太廣,本篇只分析重新整理預處理應該做的事:我們都知道,無論你載入的是哪一種上下文環境,最終都會呼叫 AbstractApplicationContext 的refresh()方法,此方法是一切載入、解析、註冊、銷燬的核心方法,採用了工廠的設計思想。

// 完成IoC容器的建立及初始化工作
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
      
      // 1: 重新整理前的準備工作。
            prepareRefresh();

            // 告訴子類重新整理內部bean 工廠。
      //  2:建立IoC容器(DefaultListableBeanFactory),載入解析XML檔案(最終儲存到Document物件中)
      // 讀取Document物件,並完成BeanDefinition的載入和註冊工作
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      //  3: 對IoC容器進行一些預處理(設定一些公共屬性)
            prepareBeanFactory(beanFactory);

            try {
    
                //  4:  允許在上下文子類中對bean工廠進行後處理。
                                postProcessBeanFactory(beanFactory);

                //  5: 呼叫BeanFactoryPostProcessor後置處理器對BeanDefinition處理
                invokeBeanFactoryPostProcessors(beanFactory);

                //  6: 註冊BeanPostProcessor後置處理器
                registerBeanPostProcessors(beanFactory);

                //  7: 初始化一些訊息源(比如處理國際化的i18n等訊息源)
                initMessageSource();

                //  8: 初始化應用事件多播器
                initApplicationEventMulticaster();
        
                //  9: 初始化一些特殊的bean
                onRefresh();

                //  10: 註冊一些監聽器
                registerListeners();

                //  11: 例項化剩餘的單例bean(非懶載入方式)
        //      注意事項:Bean的IoC、DI和AOP都是發生在此步驟
                finishBeanFactoryInitialization(beanFactory);

                //  12: 完成重新整理時,需要釋出對應的事件
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // 銷燬已經建立的單例避免佔用資源
                destroyBeans();

                // 重置'active' 標籤。
                cancelRefresh(ex);

                // 傳播異常給呼叫者
                throw ex;
            }

            finally {

                // 重置Spring核心中的常見內省快取,因為我們可能不再需要單例bean的後設資料了...
                resetCommonCaches();
            }
        }
    }

重新整理容器之重新整理預處理

​ 此步驟的主要作用在於:準備重新整理的上下文,設定啟動的時間和active的標誌作為扮演屬性資源初始化的角色。

protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // 初始化environment 上下文中的佔位符屬性資源
        initPropertySources();
        // 驗證標記為必需的所有屬性是否可解析
        getEnvironment().validateRequiredProperties();

        // 允許收集早期的ApplicationEvents
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

這裡面有兩處程式碼需要說明:initPropertySources這個方法是需要子類進行實現的,預設是不會做任何事情的;getEnvironment() 這個方法由於上述的原始碼分析過程中,已經預設建立了 createEnvironment,所以這段程式碼是直接返回的

@Override
public ConfigurableEnvironment getEnvironment() {
  if (this.environment == null) {
    this.environment = createEnvironment();
  }
  return this.environment;
}

下面只剩下了validateRequiredProperties()的分析,不著急,看原始碼不能著急,要懷著這個世界很美好的心情去看。

首先在 ConfigurablePropertyResolver 介面中定義了 validateRequiredProperties 方法

// 驗證每一個被setRequiredProperties 設定的屬性存在並且解析非空值,會丟擲
// MissingRequiredPropertiesException 異常如果任何一個需要的屬性沒有被解析。
void validateRequiredProperties() throws MissingRequiredPropertiesException;

在抽象子類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 進行新增操作,也就是如下:

@Override
public void setRequiredProperties(String... requiredProperties) {
  if (requiredProperties != null) {
    for (String key : requiredProperties) {
      this.requiredProperties.add(key);
    }
  }
}

所以,此時的 requiredProperties 這個set集合是null, 也就不存在沒有解析的元素了。

本篇到此就結束了,下一篇文章會進行原始碼分析的下一個步驟: 建立IOC容器以及Bean的解析

相關文章