【spring源碼分析】IOC容器初始化——查漏補缺(五)

前言:咱們知道在Spring中常常使用配置文件的形式對進行屬性的賦值,那配置文件的值是怎麼賦值到屬性上的呢,本文將對其進行分析。html


首先了解一個類:PropertySourcesPlaceholderConfigurer,該類對程序中使用佔位符的方式對屬性進行賦值的形式進行解析,如在xml配置文件中進行了key-value的配置,在程序中使用該配置的值的形式。spring

分析:數組

從PropertySourcesPlaceholderConfigurer的繼承結構圖上可知,PropertySourcesPlaceholderConfigurer間接實現了Aware和BeanFactoryPostProcessor兩大接口,這裏只關注BeanFactoryPostProcessor(Aware接口前面已經分析了),BeanFactoryPostProcessor接口中就只有一個postProcessBeanFactory方法,其實現以下:app

 1 // PropertySourcesPlaceholderConfigurer
 2 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
 3         // 若是propertySources爲null,則初始化該屬性
 4         if (this.propertySources == null) {
 5             this.propertySources = new MutablePropertySources();
 6             if (this.environment != null) {
 7                 this.propertySources.addLast(
 8                     new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
 9                         @Override
10                         @Nullable
11                         public String getProperty(String key) {
12                             return this.source.getProperty(key);
13                         }
14                     }
15                 );
16             }
17             try {
18                 // 構建localPropertySource對象,Properties經過mergeProperties方法獲取。
19                 PropertySource<?> localPropertySource =
20                         new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
21                 if (this.localOverride) {
22                     this.propertySources.addFirst(localPropertySource);
23                 }
24                 else {
25                     this.propertySources.addLast(localPropertySource);
26                 }
27             }
28             catch (IOException ex) {
29                 throw new BeanInitializationException("Could not load properties", ex);
30             }
31         }
32         // 進行佔位符替換
33         processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
34         this.appliedPropertySources = this.propertySources;
35     }

分析:ide

  • 首先須要構建MutablePropertySources對象,用於存儲Properties,注意Properties是經過mergeProperties獲取。
  • 佔位符的替換過程在processProperties函數中實現。

PropertiesLoaderSupport#mergeProperties函數

 1 protected Properties mergeProperties() throws IOException {
 2         // 建立Properties對象
 3         Properties result = new Properties();
 4 
 5         // 是否容許覆蓋配置 以前
 6         if (this.localOverride) {
 7             // Load properties from file upfront, to let local properties override.
 8             loadProperties(result);
 9         }
10         
11         // 進行配置合併
12         if (this.localProperties != null) {
13             for (Properties localProp : this.localProperties) {
14                 CollectionUtils.mergePropertiesIntoMap(localProp, result);
15             }
16         }
17         // 再次進行覆蓋 以後
18         if (!this.localOverride) {
19             // Load properties from file afterwards, to let those properties override.
20             loadProperties(result);
21         }
22 
23         return result;
24     }

分析:源碼分析

這裏其實邏輯簡單,主要經過loadProperties進行配置文件的導入。post

PropertiesLoaderSupport#loadPropertiesui

 1 protected void loadProperties(Properties props) throws IOException {
 2         // 遍歷文件路徑
 3         if (this.locations != null) {
 4             for (Resource location : this.locations) {
 5                 if (logger.isTraceEnabled()) {
 6                     logger.trace("Loading properties file from " + location);
 7                 }
 8                 try {
 9                     PropertiesLoaderUtils.fillProperties(
10                             props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
11                 } catch (FileNotFoundException | UnknownHostException ex) {
12                     if (this.ignoreResourceNotFound) {
13                         if (logger.isDebugEnabled()) {
14                             logger.debug("Properties resource not found: " + ex.getMessage());
15                         }
16                     } else {
17                         throw ex;
18                     }
19                 }
20             }
21         }
22     }

分析:this

這裏代碼邏輯簡單,變量locations對象,而後經過PropertiesLoaderUtils#fillProperties方法進行屬性填充,這裏不對該方法進行分析,有興趣的能夠本身詳細看下。

PropertiesLoaderSupport#processProperties

 1 protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
 2             final ConfigurablePropertyResolver propertyResolver) throws BeansException {
 3         // 設置"${"、"}"、":"
 4         propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
 5         propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
 6         propertyResolver.setValueSeparator(this.valueSeparator);
 7 
 8         // 根據是否須要忽略不能解析的符號進行分支處理
 9         StringValueResolver valueResolver = strVal -> {
10             String resolved = (this.ignoreUnresolvablePlaceholders ?
11                     propertyResolver.resolvePlaceholders(strVal) :
12                     propertyResolver.resolveRequiredPlaceholders(strVal));
13             if (this.trimValues) {
14                 resolved = resolved.trim();
15             }
16             return (resolved.equals(this.nullValue) ? null : resolved);
17         };
18         
19         // 進行具體解析
20         doProcessProperties(beanFactoryToProcess, valueResolver);
21     }

分析:

從函數的具體邏輯能夠看出Spring中對佔位符的解析只包含"${ }"和":"。

  • 建立StringValueResolver對象,根據不一樣的策略(是否要忽略不能解析的符號),這裏會在doProcessProperties中進行回調。其實resolvePlaceholders和resolveRequiredPlaceholders函數內部都調用的同一個函數,只是其初始化方式不一樣(false和true的區別),具體代碼AbstractPropertyResolver中。
  • 最終的具體解析過程落在doProcessProperties函數中。

AbstractPropertyResolver#resolveRequiredPlaceholders

 1 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
 2         // 若是strictHelper爲null,則經過createPlaceholderHelper進行建立 
 3         // 注意這裏createPlaceholderHelper入參爲false,這就是和resolvePlaceholders不同的地方
 4         if (this.strictHelper == null) {
 5             this.strictHelper = createPlaceholderHelper(false);
 6         }
 7         // 經過PropertyPlaceholderHelper進行解析
 8         return doResolvePlaceholders(text, this.strictHelper);
 9 }
10 
11 private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
12         return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
13                 this.valueSeparator, ignoreUnresolvablePlaceholders);
14     }
15 
16 private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
17         return helper.replacePlaceholders(text, this::getPropertyAsRawString);
18     }
19 // PropertyPlaceholderHelper
20 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
21         Assert.notNull(value, "'value' must not be null");
22         return parseStringValue(value, placeholderResolver, new HashSet<>());
23     }

分析:

以上代碼邏輯仍是比較簡單,最終的解析落入PropertyPlaceholderHelper#replacePlaceHolders函數中,該函數在【spring源碼分析】IOC容器初始化(一)中已分析。

PlaceholderConfigurerSupport#doProcessProperties

 1 protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
 2             StringValueResolver valueResolver) {
 3 
 4         // 建立BeanDefinitionVisitor對象
 5         BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
 6 
 7         // 對BeanDefinition進行遍歷
 8         String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
 9         for (String curName : beanNames) {
10             // Check that we're not parsing our own bean definition,
11             // to avoid failing on unresolvable placeholders in properties file locations.
12             // 校驗
13             // 當前PlaceholderConfigurerSupport不在解析範圍內  同一個Spring容器
14             if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
15                 BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
16                 try {
17                     // 訪問bean,實際上是對屬性進行解析
18                     visitor.visitBeanDefinition(bd);
19                 }
20                 catch (Exception ex) {
21                     throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
22                 }
23             }
24         }
25 
26         // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
27         // 別名佔位符
28         beanFactoryToProcess.resolveAliases(valueResolver);
29 
30         // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
31         // 解析嵌入值的佔位符,例如註釋屬性
32         beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
33     }

分析:

  • 首先建立BeanDefinitionVisitor對象,經過字符串解析器,用於後期對配置文件進行解析。
  • 對BeanDefinition進行遍歷,經過visitBeanDefinition方法進行配置文件解析。
  • 解析別名佔位符。
  • 解析嵌入值的佔位符,例如註釋屬性。

其實整個方法的核心在於visitBeanDefinition方法。

BeanDefinitionVisitor#visitBeanDefinition

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

分析:

該方法基本訪問了BeanDefinition中全部值得訪問的東西了,包括parent 、class 、factory-bean 、factory-method 、scope 、property 、constructor-arg。

這裏關注visitPropertyValues方法。

BeanDefinitionVisitor#visitPropertyValues

 1     protected void visitPropertyValues(MutablePropertyValues pvs) {
 2         // 遍歷PropertyValue數組
 3         PropertyValue[] pvArray = pvs.getPropertyValues();
 4         for (PropertyValue pv : pvArray) {
 5             // 解析真值
 6             Object newVal = resolveValue(pv.getValue());
 7             if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
 8                 // 設置到PropertyValue中
 9                 pvs.add(pv.getName(), newVal);
10             }
11         }
12     }

分析:

該函數主要就是對屬性數組進行遍歷,經過resolveValue方法對屬性進行解析獲取最新值,若是新值與就只不相等,則用新值替換舊值。

BeanDefinitionVisitor#resolveValue

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

分析:

該函數對各類類型的屬性進行解析,因爲配置爲String類型的,所以這裏關注resolveStringValue函數。

BeanDefinitionVisitor#resolveStringValue

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

分析:

這裏具體的解析就會回調valueResolver(根據不一樣策略建立的StringValueResolver對象),而後進入具體的解析,其解析過程已經分析,這裏不在贅述。

總結

至此關於佔位符的解析過程就大體分析完了,其實裏面還有不少值得咱們細究的地方,具體過程可debug調試一遍,可能會有更深的理解。


by Shawn Chen,2019.05.08,晚。

相關文章
相關標籤/搜索