激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - .properties文件讀取及占位符${...}替換源碼解析

.properties文件讀取及占位符${...}替換源碼解析

2020-09-22 10:20五月的倉頡 Java教程

本篇文章主要介紹了.properties文件讀取及占位符${...}替換源碼解析的相關(guān)知識(shí),具有很好的參考價(jià)值。下面跟著小編一起來看下吧

前言

我們?cè)陂_發(fā)中常遇到一種場景,Bean里面有一些參數(shù)是比較固定的,這種時(shí)候通常會(huì)采用配置的方式,將這些參數(shù)配置在.properties文件中,然后在Bean實(shí)例化的時(shí)候通過Spring將這些.properties文件中配置的參數(shù)使用占位符"${}"替換的方式讀入并設(shè)置到Bean的相應(yīng)參數(shù)中。

這種做法最典型的就是JDBC的配置,本文就來研究一下.properties文件讀取及占位符"${}"替換的源碼,首先從代碼入手,定義一個(gè)DataSource,模擬一下JDBC四個(gè)參數(shù):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class DataSource {
 
  /**
   * 驅(qū)動(dòng)類
   */
  private String driveClass;
 
  /**
   * jdbc地址
   */
  private String url;
 
  /**
   * 用戶名
   */
  private String userName;
 
  /**
   * 密碼
   */
  private String password;
 
  public String getDriveClass() {
    return driveClass;
  }
 
  public void setDriveClass(String driveClass) {
    this.driveClass = driveClass;
  }
 
  public String getUrl() {
    return url;
  }
 
  public void setUrl(String url) {
    this.url = url;
  }
 
  public String getUserName() {
    return userName;
  }
 
  public void setUserName(String userName) {
    this.userName = userName;
  }
 
  public String getPassword() {
    return password;
  }
 
  public void setPassword(String password) {
    this.password = password;
  }
 
  @Override
  public String toString() {
    return "DataSource [driveClass=" + driveClass + ", url=" + url + ", userName=" + userName + ", password=" + password + "]";
  }
}

定義一個(gè)db.properties文件:

?
1
2
3
4
driveClass=0
url=1
userName=2
password=3

定義一個(gè)properties.xml文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 
  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="properties/db.properties"></property>
  </bean>
 
  <bean id="dataSource" class="org.xrq.spring.action.properties.DataSource">
    <property name="driveClass" value="${driveClass}" />
    <property name="url" value="${url}" />
    <property name="userName" value="${userName}" />
    <property name="password" value="${password}" />
  </bean>
</beans>

寫一段測試代碼:

?
1
2
3
4
5
6
7
8
9
10
public class TestProperties {
 
  @Test
  public void testProperties() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring/properties.xml");
 
    DataSource dataSource = (DataSource)ac.getBean("dataSource");
    System.out.println(dataSource);
  }
}

運(yùn)行結(jié)果就不貼了,很明顯,下面就來分析一下Spring是如何將properties文件中的屬性讀入并替換"${}"占位符的。

PropertyPlaceholderConfigurer類解析

在properties.xml文件中我們看到了一個(gè)類PropertyPlaceholderConfigurer,顧名思義它就是一個(gè)屬性占位符配置器,看一下這個(gè)類的繼承關(guān)系圖:

.properties文件讀取及占位符${...}替換源碼解析

看到從這張圖上,我們能分析出來的最重要的一點(diǎn)就是PropertyPlaceholderConfigurer是BeanFactoryPostProcessor接口的實(shí)現(xiàn)類,想見Spring上下文必然是在Bean定義全部加載完畢后且Bean實(shí)例化之前通過postProcessBeanFactory方法一次性地替換了占位符"${}"

.properties文件讀取源碼解析

下面來看一下postProcessBeanFactory方法實(shí)現(xiàn):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  try {
    Properties mergedProps = mergeProperties();
 
    // Convert the merged properties, if necessary.
    convertProperties(mergedProps);
    // Let the subclass process the properties.
    processProperties(beanFactory, mergedProps);
  }
  catch (IOException ex) {
    throw new BeanInitializationException("Could not load properties", ex);
  }
}

跟一下第3行的mergeProperties方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected Properties mergeProperties() throws IOException {
  Properties result = new Properties();
 
  if (this.localOverride) {
    // Load properties from file upfront, to let local properties override.
    loadProperties(result);
  }
 
  if (this.localProperties != null) {
    for (Properties localProp : this.localProperties) {
      CollectionUtils.mergePropertiesIntoMap(localProp, result);
    }
  }
 
  if (!this.localOverride) {
    // Load properties from file afterwards, to let those properties override.
    loadProperties(result);
  }
  return result;
}

第2行的方法new出一個(gè)Properties,名為result,這個(gè)result會(huì)隨著之后的代碼傳入,.properties文件中的數(shù)據(jù)會(huì)寫入result中。

OK,接著看,代碼進(jìn)入第17行的方法,通過文件加載.properties文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
protected void loadProperties(Properties props) throws IOException {
  if (this.locations != null) {
    for (Resource location : this.locations) {
      if (logger.isInfoEnabled()) {
        logger.info("Loading properties file from " + location);
      }
      InputStream is = null;
      try {
        is = location.getInputStream();
 
        String filename = null;
        try {
          filename = location.getFilename();
        } catch (IllegalStateException ex) {
          // resource is not file-based. See SPR-7552.
        }
 
        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
          this.propertiesPersister.loadFromXml(props, is);
        }
        else {
          if (this.fileEncoding != null) {
            this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding));
          }
          else {
            this.propertiesPersister.load(props, is);
          }
        }
      }
      catch (IOException ex) {
        if (this.ignoreResourceNotFound) {
          if (logger.isWarnEnabled()) {
            logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
          }
        }
        else {
          throw ex;
        }
      }
      finally {
        if (is != null) {
          is.close();
        }
      }
    }
  }
}

第9行,PropertyPlaceholderConfigurer的配置可以傳入路徑列表(當(dāng)然這里只傳了一個(gè)db.properties),第3行遍歷列表,第9行通過一個(gè)輸入字節(jié)流InputStream獲取.properties對(duì)應(yīng)的二進(jìn)制數(shù)據(jù),然后第23行的代碼將InputStream中的二進(jìn)制解析,寫入第一個(gè)參數(shù)Properties中,Properties是JDK原生的讀取.properties文件的工具。

就這樣一個(gè)簡單的流程,將.properties中的數(shù)據(jù)進(jìn)行了解析,并寫入result中(result是mergeProperties方法中new出的一個(gè)Properties)。

占位符"${...}"替換源碼解析

上面看了.properties文件讀取流程,接著就應(yīng)當(dāng)替換"${}"占位符了,還是回到postProcessBeanFactory方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  try {
    Properties mergedProps = mergeProperties();
 
    // Convert the merged properties, if necessary.
    convertProperties(mergedProps);
    // Let the subclass process the properties.
    processProperties(beanFactory, mergedProps);
  }
  catch (IOException ex) {
    throw new BeanInitializationException("Could not load properties", ex);
  }
}

第3行合并了.properties文件(之所以叫做合并是因?yàn)槎鄠€(gè).properties文件中可能有相同的Key)。

第6行在必要的情況下對(duì)合并的Properties進(jìn)行轉(zhuǎn)換,沒看出有什么用。

第9行就開始替換占位符"${...}"了,要事先聲明一點(diǎn):BeanFactoryPostProcessor類的postProcessBeanFactory方法調(diào)用是在Bean定義解析之后,因此當(dāng)前的beanFactory參數(shù)中已經(jīng)有了所有的Bean定義,如果熟悉Bean解析流程的朋友對(duì)這一點(diǎn)應(yīng)該很清楚。跟一下第9行的processProperties方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
    throws BeansException {
 
  StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
  BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
 
  String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
  for (String curName : beanNames) {
    // Check that we're not parsing our own bean definition,
    // to avoid failing on unresolvable placeholders in properties file locations.
    if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
      BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
      try {
        visitor.visitBeanDefinition(bd);
      }
      catch (Exception ex) {
        throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage());
      }
    }
  }
 
  // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
  beanFactoryToProcess.resolveAliases(valueResolver);
  // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
  beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

第4行new出一個(gè)PlaceholderResolvingStringValueResolver,傳入Properties,顧名思義這是一個(gè)持有.properties文件配置的字符串值解析器。

第5行BeanDefinitionVistor,傳入上面的StringValueResolver,顧名思義這是一個(gè)Bean定義訪問工具,持有字符串值解析器,想見可以通過BeanDefinitionVistor訪問Bean定義,在遇到需要解析的字符串的時(shí)候使用構(gòu)造函數(shù)傳入的StringValueResolver解析字符串

第7行通過BeanFactory獲取所有Bean定義的名稱。

第8行開始遍歷所有Bean定義的名稱,注意第11行的第一個(gè)判斷"!(curName.equals(this.beanName)" ,this.beanName指的是PropertyPlaceholderConfigurer,意為PropertyPlaceholderConfigurer本身不會(huì)去解析占位符"${...}"。

著重跟14行的代碼,BeanDefinitionVistor的visitBeanDefinition方法,傳入BeanDefinition:

?
1
2
3
4
5
6
7
8
9
10
11
public void visitBeanDefinition(BeanDefinition beanDefinition) {
  visitParentName(beanDefinition);
  visitBeanClassName(beanDefinition);
  visitFactoryBeanName(beanDefinition);
  visitFactoryMethodName(beanDefinition);
  visitScope(beanDefinition);
  visitPropertyValues(beanDefinition.getPropertyValues());
  ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
  visitIndexedArgumentValues(cas.getIndexedArgumentValues());
  visitGenericArgumentValues(cas.getGenericArgumentValues());
}

看到這個(gè)方法輪番訪問<bean>定義中的parent、class、factory-bean、factory-method、scope、property、constructor-arg屬性,但凡遇到需要"${...}"就進(jìn)行解析。我們這里解析的是property標(biāo)簽中的"${...}",因此跟一下第7行的代碼:

?
1
2
3
4
5
6
7
8
9
protected void visitPropertyValues(MutablePropertyValues pvs) {
  PropertyValue[] pvArray = pvs.getPropertyValues();
  for (PropertyValue pv : pvArray) {
    Object newVal = resolveValue(pv.getValue());
    if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
      pvs.add(pv.getName(), newVal);
    }
  }
}

獲取屬性數(shù)組進(jìn)行遍歷,第4行的代碼對(duì)屬性值進(jìn)行解析獲取新屬性值,第5行判斷新屬性值與原屬性值不等,第6行的代碼用新屬性值替換原屬性值。因此跟一下第4行的resolveValue方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protected Object resolveValue(Object value) {
  if (value instanceof BeanDefinition) {
    visitBeanDefinition((BeanDefinition) value);
  }
  else if (value instanceof BeanDefinitionHolder) {
    visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
  }
  else if (value instanceof RuntimeBeanReference) {
    RuntimeBeanReference ref = (RuntimeBeanReference) value;
    String newBeanName = resolveStringValue(ref.getBeanName());
    if (!newBeanName.equals(ref.getBeanName())) {
      return new RuntimeBeanReference(newBeanName);
    }
  }
  else if (value instanceof RuntimeBeanNameReference) {
    RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
    String newBeanName = resolveStringValue(ref.getBeanName());
    if (!newBeanName.equals(ref.getBeanName())) {
      return new RuntimeBeanNameReference(newBeanName);
    }
  }
  else if (value instanceof Object[]) {
    visitArray((Object[]) value);
  }
  else if (value instanceof List) {
    visitList((List) value);
  }
  else if (value instanceof Set) {
    visitSet((Set) value);
  }
  else if (value instanceof Map) {
    visitMap((Map) value);
  }
  else if (value instanceof TypedStringValue) {
    TypedStringValue typedStringValue = (TypedStringValue) value;
    String stringValue = typedStringValue.getValue();
    if (stringValue != null) {
      String visitedString = resolveStringValue(stringValue);
      typedStringValue.setValue(visitedString);
    }
  }
  else if (value instanceof String) {
    return resolveStringValue((String) value);
  }
  return value;
}

這里主要對(duì)value類型做一個(gè)判斷,我們配置文件里面配置的是字符串,因此就看字符串相關(guān)代碼,即34行的判斷進(jìn)去,其余的差不多,可以自己看一下源碼是怎么做的。第35~第36行的代碼就是獲取屬性值,第38行的代碼resolveStringValue方法解析字符串:

?
1
2
3
4
5
6
7
8
9
protected String resolveStringValue(String strVal) {
  if (this.valueResolver == null) {
    throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
        "object into the constructor or override the 'resolveStringValue' method");
  }
  String resolvedValue = this.valueResolver.resolveStringValue(strVal);
  // Return original String if not modified.
  return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}

繼續(xù)跟第6行的方法,valueResolver前面說過了,是傳入的一個(gè)PlaceholderResolvingStringValueResolver,看一下resolveStringValue方法實(shí)現(xiàn):

?
1
2
3
4
public String resolveStringValue(String strVal) throws BeansException {
  String value = this.helper.replacePlaceholders(strVal, this.resolver);
  return (value.equals(nullValue) ? null : value);
}

第2行的replacePlaceholders方法顧名思義,替換占位符,它位于PropertyPlaceholderHelper類中,跟一下這個(gè)方法:

?
1
2
3
4
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
   Assert.notNull(value, "Argument 'value' must not be null.");
   return parseStringValue(value, placeholderResolver, new HashSet<String>());
}

繼續(xù)跟第3行的parseStringValue方法,即追蹤到了替換占位符的核心代碼中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
protected String parseStringValue(
    String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
 
  StringBuilder buf = new StringBuilder(strVal);
 
  int startIndex = strVal.indexOf(this.placeholderPrefix);
  while (startIndex != -1) {
    int endIndex = findPlaceholderEndIndex(buf, startIndex);
    if (endIndex != -1) {
      String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
      if (!visitedPlaceholders.add(placeholder)) {
        throw new IllegalArgumentException(
            "Circular placeholder reference '" + placeholder + "' 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);
        buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
        if (logger.isTraceEnabled()) {
          logger.trace("Resolved placeholder '" + placeholder + "'");
        }
        startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
      }
      else if (this.ignoreUnresolvablePlaceholders) {
        // Proceed with unprocessed value.
        startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
      }
      else {
        throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'");
      }
 
      visitedPlaceholders.remove(placeholder);
    }
    else {
      startIndex = -1;
    }
  }
 
  return buf.toString();
}

過一下此流程:

  1. 獲取占位符前綴"${"的位置索引startIndex
  2. 占位符前綴"${"存在,從"${"后面開始獲取占位符后綴"}"的位置索引endIndex
  3. 如果占位符前綴位置索引startIndex與占位符后綴的位置索引endIndex都存在,截取中間的部分placeHolder
  4. 從Properties中獲取placeHolder對(duì)應(yīng)的值propVal
  5. 如果propVal不存在,嘗試對(duì)placeHolder使用":"進(jìn)行一次分割,如果分割出來有結(jié)果,那么前面一部分命名為actualPlaceholder,后面一部分命名為defaultValue,嘗試從Properties中獲取actualPlaceholder對(duì)應(yīng)的value,如果存在則取此value,如果不存在則取defaultValue,最終賦值給propVal
  6. 返回propVal,就是替換之后的值

流程很長,通過這樣一整個(gè)的流程,將占位符"${...}"中的內(nèi)容替換為了我們需要的值。

以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持服務(wù)器之家!

原文鏈接:http://www.cnblogs.com/xrq730/p/6785473.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 牛牛a级毛片在线播放 | 羞羞网站在线看 | 久久成人动漫 | 欧美另类视频在线 | 国产精品久久久久免费视频 | 55夜色66夜色国产精品视频 | 天天色综合2 | 成人毛片在线 | 国产高清毛片 | 狠狠干天天操 | 牛牛碰在线 | 羞羞视频免费网站 | 在线播放免费播放av片 | 精品国产96亚洲一区二区三区 | 99极品视频 | 国产精品自拍99 | 亚洲小视频在线观看,com | 成人在线观看免费视频 | 免费国产不卡午夜福在线 | 日本韩国欧美一级片 | www噜噜偷拍在线视频 | 黄污网站在线 | 国产精品久久久久影院老司 | 最新中文字幕第一页视频 | av在线免费网 | 国产精品久久久免费观看 | 手机国产乱子伦精品视频 | 精品成人国产在线观看男人呻吟 | 亚洲精品无码不卡在线播放he | 91精品国产九九九久久久亚洲 | 精品乱码久久久久 | 夜班护士在线观看 | av电影网站在线 | 美女视频黄视大全视频免费网址 | 一本色道久久综合狠狠躁篇适合什么人看 | 久国久产久精永久网页 | 日韩欧美视频一区二区三区 | 欧美日韩在线视频一区 | 欧美三日本三级少妇三级99观看视频 | 久久久久97国产精 | 一级国产电影 |