Spring IOC源碼分析之-刷新前的準備工做

ClassPathXmlApplicationContext的註冊方式

源碼分析基於Spring4.3程序員

ClassPathXmlApplicationContext入口,最終都會調用到web

/*
     * 使用給定父級建立新的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");

就等同於app

手動刷新框架

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

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

加載父子容器

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

  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 抽象類,下面是具體的繼承樹:

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) 以後,會變成下圖所示
    • 如何獲取系統屬性和如何獲取系統環境沒有往下跟,有興趣的讀者能夠繼續沿用。

      大體截一個圖,裏面大概的屬性是這樣

      systemProperties

      systemEnvironment

  • 另一個是 resolveRequiredPlaceholders,它是由 PropertyResolver 超頂級接口定義的方法
// 在給定的text 參數中解析${} 佔位符,將其替換爲getProperty 解析的相應屬性值。
// 沒有默認值的沒法解析的佔位符將致使拋出IllegalArgumentException。
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
    • AbstractPropertyResolver 子類來實現,且看AbstractPropertyResolver 的繼承樹

    -

    具體實現的方法以下:

    // 傳遞進來的文本就是解析過的 配置文件 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的解析

相關文章
相關標籤/搜索