Spring——ClassPathXmlApplicationContext(配置文件路徑解析 1)


ClassPathXmlApplicationContext

      在我的 BeanFactory 容器 文章中主要提及了 BeanFactory 容器初始化(Spring 配置文件加載(還沒解析))的一些過程結合源碼進行分析。那么,本篇文章主要對

  ClassPathXmlApplicationContext cp = new ClassPathXmlApplicationContext("bean3.xml");

  Spring 配置文件路徑占位符加載、解析的過程結合源碼進行分析,本篇也不會對 配置文件的解析過程進行分析,解析過程我會在后面再進行說明,因為涉及到的東西實在太多了!

  使用 BeanFactory 和 ClassPathXmlApplicationContext 在加載配置文件時是有相同操作的。

 

ClassPathXmlApplicationContext關系圖

Spring 文檔

                            (Spring 文檔關系圖)

UML

  

                           (UML 圖)

構造方法

      實例化 ClassPathXmlApplicationContext 傳入一個字符串(配置文件名稱),所以調用下面構造方法

  

                              ((String configLocation) 構造方法)

    之后調用本類的另一個構造方法

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
     //一直調用父類構造,直到 AbstractApplicationContext,設置一個空的 ApplicationContext 對象 super(parent);
     //設置配置文件路徑 setConfigLocations(configLocations);
     //默認為 true
if (refresh) { refresh(); } }

    父類 AbstractApplicationContext 構造方法

public AbstractApplicationContext(ApplicationContext parent) {
  //調用本類的無參構造
this(); setParent(parent); }

    AbstractApplicationContext 無參構造方法

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

    AbstractApplicationContext.getResourcePatternResolver()

protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

    從 PathMatchingResourcePatternResolver 類上的注釋可知 該類支持 Ant 風格的路徑解析。

 

設置配置文件路徑

      在 ClassPathXmlApplicationContext 構造方法中調用了 setConfigLocations(配置文件路徑數組)

    setConfigLocations(configLocations)

public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
       //configLocations 是一個 String 類型的數組
this.configLocations = new String[locations.length];
       //遍歷路徑數組,將 解析(分解) 后的路徑放到 configLocations 中
for (int i = 0; i < locations.length; i++) {
          //resolvePath() 方法用於解析路徑
this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }

 

    resolvePath()方法

protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }

    getEnvironment() 是 ConfigurableApplicationContext 接口的方法,AbstractApplicationContext 對其進行了重寫

    

                 (getEnvironment() 方法)

    很簡單,當前 ConfigurableEnvironment 對象為空就創建一個,不為空就直接返回。

  注意

    上面說到了創建一個 ConfigurableEnvironment 對象返回,但是其中還發生了一些其他事情,如下

      首先是 createEnvironment() 方法,很簡單,就創建一個 StandardEnvironment 對象

protected ConfigurableEnvironment createEnvironment() {
    return new StandardEnvironment();
}

      但是,StandardEnvironment 類繼承自 AbstractEnvironment 類,StandardEnvironment 類只有一個無參的構造方法,所以,在初始化 StandardEnvironment 時,便會調用父類 AbstractEnvironment 類的無參構造方法,如下

public AbstractEnvironment() {
  //調用方法 customizePropertySources(
this.propertySources); //一個日志輸出 }

       StandardEnvironment 類對 customizePropertySources() 方法進行了重寫,下面就是來到重寫后的 customizePropertySources() 方法

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
  /*
   SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME: systemProperties
   SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME: systemEnvironment
  */ propertySources.addLast(
new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }

      AbstractEnvironment.getSystemProperties(),如下

public Map<String, Object> getSystemProperties() {
    try {
     //首先獲取全部屬性
return (Map) System.getProperties(); } catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @Override protected String getSystemAttribute(String attributeName) { try {
            //如果獲取全部屬性失敗,則獲取單個指定屬性
            //如果獲取單個還是不行則拋出異常
return System.getProperty(attributeName); } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { logger.info(format("Caught AccessControlException when accessing system " + "property [%s]; its value will be returned [null]. Reason: %s", attributeName, ex.getMessage())); } return null; } } }; } }

      getSystemEnvironment()方法的邏輯和 getSystemProperties() 方法的實現差不多的。只是最后使用 System.getenv() 來獲取。

   Environment

    Environment是一個接口,代表了當前應用程序所處的環境,從下面 UML 圖中的大部分方法中可以看出 該接口主要和 profile、property有關。

    

          (http://www.cnblogs.com/dream-saddle/gallery/image/215409.html)

    profile

      profile 是 Spring3.1開始有的。詳情見下面幾篇文章吧

       https://www.cnblogs.com/strugglion/p/7091021.html

      另外我們可以使用下面這種方式進行設置當前使用的 profile

     

                          (設置 profile)

    property

      property 就是應用程序運行時的參數

 

  配置文件路徑中 placeholder(占位符)解析

    為什么需要解析 placeholder?因為我們可以這樣寫:

//自定義一個系統屬性,名為 spring 值為 *1(PathMatchingResourcePatternResolver 支持 Ant 風格的路徑解析) 或配置文件全名
System.setProperty("spring", "*1");
//使用占位符設置配置文件路徑 ClassPathXmlApplicationContext cp
= new ClassPathXmlApplicationContext("${spring}.xml"); //通過下面步驟的解析,最后得到的路徑就是 *1.xml

    resolveRequiredPlaceholders(path)

      該方法是 PropertyResovler 接口定義(該接口關系圖在上面已經有了)的方法,所以,來到其實現類 AbstractEnvironment 進行了重寫。

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
  //propertyResolver private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
  //text 就是傳過來的配置文件路徑 如 classpath:bean.xml
return this.propertyResolver.resolveRequiredPlaceholders(text); }

      繼續跟蹤來到 AbstractPropertyResolver.resolveRequiredPlaceholders(String text)

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
  //strictHelper 是 PropertyPlaceholderHelper 對象,如果為空就創建一個
if (this.strictHelper == null) { this.strictHelper = createPlaceholderHelper(false); }
  //開始解析占位符
return doResolvePlaceholders(text, this.strictHelper); }

      AbstractPropertyResolver.createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders)

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
  /*
  placeholderPrefix: ${
  placholderSuffix: }
  valuleSeparator: :
  ignoreUnresolvablePlaceholders: 默認 false
  */
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders); }

      AbstractPropertyResolver.doResolverPlaceholders(String text, PropertyPlaceholderHelper helper)

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

      PropertyPlaceholderHelper.replacePlaceholders(String value, PlaceholderResolver placeholderResolver)

/**
     * Replaces all placeholders of format {@code ${name}} with the value returned
     * from the supplied {@link PlaceholderResolver}.
   * 替換所有的占位符
*/ public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "'value' must not be null"); return parseStringValue(value, placeholderResolver, new HashSet<String>()); }

      繼續調用本類的 parseStringValue() 方法,這個方法代碼挺多的,這里只說明幾個重要的部分吧,請讀者結合源碼閱讀!

/*
this.placeholderPrefix: ${
this.placeholderSuffix: }
this.valueSeparator: :
*/
protected
String parseStringValue( String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(strVal);
  //獲取 ${ 在路徑中的位置,用於判斷是否需要進行解析,如果沒有就直接返回
int startIndex = strVal.indexOf(this.placeholderPrefix); while (startIndex != -1) {
     //獲取占位符字符串的最后一個字符串索引 如:${config}.xml,就是 ${config} 的長度
     //獲取長度只是個客觀說法,但是實際邏輯並不是這樣,而是獲取 "}" 的位置,具體還請讀者跟進方法查看
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 ...; } // Recursive invocation, parsing placeholders contained in the placeholder key.
       //遞歸調用了一次
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key...
       //根據占位符從 systemProperties,systemEnvironment 中獲取值,獲取的就是 System.setProperty(key, value) 中的 value
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()) { ...; } 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 ...; } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); }

      到這里,整個配置文件路徑的解析,設置就結束了。

      System.setProperty("spring", "*1");
           ApplicationContext cp = new ClassPathXmlApplicationContext("${spring}.xml");

      就是將 ${spring}.xml 中的 spring 解析出來,然后替換為 *1,最后存入到 AbstractRefreshableConfigApplicationContext 類的 configLocations 數組中。

      接着就是讀取配置文件、解析、注冊Bean,太多了,還是等到后面再說吧!

      我們已經知道 setConfigLocations(configLocations) 方法可以將 ${xxx}.xml 這樣的 xml 配置解析為 xxx.xml 了,但是這個 xxx 可以是我們隨意指定的 一個 Ant 風格的路徑,那又是怎么解析的呢? 接着就是 refresh() 方法,后面會介紹到!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM