歡迎光臨
每天分享高質量文章

【死磕 Spring】—– IOC 之深入分析 PropertyOverrideConfigurer

在文章 【死磕 Spring】—– IOC 之 深入分析 BeanFactoryPostProcessor 中提到,BeanFactoryPostProcessor 作用與 bean 完成加載之後與 bean 實體化之前,是 Spring 提供的一種強大的擴展機制,他有兩個重要的子類,一個是 PropertyPlaceholderConfigurer,另一個是 PropertyOverrideConfigurer ,其中 PropertyPlaceholderConfigurer 允許我們通過配置 Properties 的方式來取代 bean 中定義的占位符,而 PropertyOverrideConfigurer 呢?正是我們這篇博客介紹的。

PropertyOverrideConfigurer 允許我們對 Spring 容器中配置的任何我們想處理的 bean 定義的 property 信息進行改寫替換。

這個定義聽起來有點兒玄乎,通俗點說就是我們可以通過 PropertyOverrideConfigurer 來改寫任何 bean 中的任何屬性,只要我們想。

PropertyOverrideConfigurer 的使用規則是 beanName.propertyName=value,這裡需要註意的是 beanName,propertyName 則是該 bean 中存在的屬性

使用

依然使用以前的例子,Student.class,我們只需要修改下配置檔案,宣告下 PropertyOverrideConfigurer 以及其加載的配置檔案。如下:

  1.     class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
  2.         name="locations">
  3.            
  4.                classpath:application.properties
  •            
  •        
  •    
  •  
  •     id="student" class="org.springframework.core.service.StudentService">
  •         name="name" value="chenssy"/>
  •    

指定 student 的 name 屬性值為 chenssy,宣告 PropertyOverrideConfigurer 加載的檔案為 application.properties,內容如下:

  1. student.name = chenssy-PropertyOverrideConfigurer

指定 beanName 為 student 的 bean 的 name 屬性值為 chenssy-PropertyOverrideConfigurer。

測試打印 student 中的 name 屬性值,如下:

  1. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  2.  
  3. StudentService studentService = (StudentService) context.getBean("student");
  4. System.out.println("student name:" + studentService.getName());

運行結果為:

從中可以看出 PropertyOverrideConfigurer 定義的檔案取代了 bean 中預設的值。

下麵我們看一個有趣的例子,如果我們一個 bean 中 PropertyPlaceholderConfigurer 和 PropertyOverrideConfigurer 都使用呢?那是顯示誰定義的值呢?這裡先簡單分析下:如果PropertyOverrideConfigurer 先作用,那麼 PropertyPlaceholderConfigurer 在匹配占位符的時候就找不到了,如果 PropertyOverrideConfigurer 後作用,也會直接取代 PropertyPlaceholderConfigurer 定義的值,所以無論如何都會顯示 PropertyOverrideConfigurer 定義的值。是不是這樣呢?看如下例子:

xml 配置檔案調整如下:

  1.     class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
  2.         name="locations">
  3.            
  4.                classpath:application1.properties
  •            
  •        
  •    
  •  
  •     class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  •         name="locations">
  •            
  •                classpath:application2.properties
  •            
  •        
  •    
  •  
  •     id="student" class="org.springframework.core.service.StudentService">
  •         name="name" value="${studentService.name}"/>
  •    

指定 PropertyPlaceholderConfigurer 加載檔案為 application1.properties,PropertyPlaceholderConfigurer 加載檔案為 application2.properties,student 的 name 屬性使用占位符 ${studentService.name}。配置檔案內容為:

  1. application1.properties
  2. student.name = chenssy-PropertyOverrideConfigurer
  3.  
  4. application2.properties
  5. studentService.name = chenssy-PropertyPlaceholderConfigurer

測試程式依然是打印 name 屬性值,運行結果如下:

所以,上面的分析沒有錯。下麵我們來分析 PropertyOverrideConfigurer 實現原理。其實如果瞭解 PropertyPlaceholderConfigurer 的實現機制的話,那麼 PropertyOverrideConfigurer 也不難猜測:加載指定 Properties,迭代其中的屬性值,依據 “.” 來得到 beanName(split(“.”)[0]),從容器中獲取指定的 BeanDefinition,然後得到 name 屬性,進行替換即可。

實現原理

UML 結構圖如下:

與 PropertyPlaceholderConfigurer 一樣,也是繼承 PropertyResourceConfigurer,我們知道 PropertyResourceConfigurer 對 BeanFactoryPostProcessor 的 postProcessBeanFactory() 提供了實現,在該實現中它會去讀取指定配置檔案中的內容,然後 processProperties() ,該方法是一個抽象方法,具體的實現由子類來實現,所以這裡我們只需要看 PropertyOverrideConfigurer 中 processProperties() 中的具體實現,如下:

  1.    protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
  2.            throws BeansException {
  3.        // 迭代配置檔案中的內容
  4.        for (Enumeration> names = props.propertyNames(); names.hasMoreElements();) {
  5.            String key = (String) names.nextElement();
  6.            try {
  7.                processKey(beanFactory, key, props.getProperty(key));
  8.            }
  9.            catch (BeansException ex) {
  10.                String msg = "Could not process key '" + key + "' in PropertyOverrideConfigurer";
  11.                if (!this.ignoreInvalidKeys) {
  12.                    throw new BeanInitializationException(msg, ex);
  13.                }
  14.                if (logger.isDebugEnabled()) {
  15.                    logger.debug(msg, ex);
  16.                }
  17.            }
  18.        }
  19.    }

迭代 props 內容,依次呼叫 processKey(),如下:

  1.    protected void processKey(ConfigurableListableBeanFactory factory, String key, String value)
  2.            throws BeansException {
  3.  
  4.        // 判斷是否存在 "."
  5.        // 獲取其索引位置
  6.        int separatorIndex = key.indexOf(this.beanNameSeparator);
  7.        // 如果不存在,. 則丟擲異常
  8.        if (separatorIndex == -1) {
  9.            throw new BeanInitializationException("Invalid key '" + key +
  10.                    "': expected 'beanName" + this.beanNameSeparator + "property'");
  11.        }
  12.  
  13.        // 得到 beanName
  14.        String beanName = key.substring(0, separatorIndex);
  15.        // 得到屬性值
  16.        String beanProperty = key.substring(separatorIndex+1);
  17.        this.beanNames.add(beanName);
  18.        // 替換
  19.        applyPropertyValue(factory, beanName, beanProperty, value);
  20.        if (logger.isDebugEnabled()) {
  21.            logger.debug("Property '" + key + "' set to value [" + value + "]");
  22.        }
  23.    }

獲取分割符 “.” 的索引位置,得到 beanName 以及相應的屬性,然後呼叫 applyPropertyValue(),如下:

  1. protected void applyPropertyValue(
  2.   ConfigurableListableBeanFactory factory, String beanName, String property, String value) {
  3.  
  4.  BeanDefinition bd = factory.getBeanDefinition(beanName);
  5.  BeanDefinition bdToUse = bd;
  6.  while (bd != null) {
  7.   bdToUse = bd;
  8.   bd = bd.getOriginatingBeanDefinition();
  9.  }
  10.  PropertyValue pv = new PropertyValue(property, value);
  11.  pv.setOptional(this.ignoreInvalidKeys);
  12.  bdToUse.getPropertyValues().addPropertyValue(pv);
  13. }

從容器中獲取 BeanDefinition ,然後根據屬性 property 和 其值 value 構造成一個 PropertyValue 物件,最後呼叫 addPropertyValue() 方法。PropertyValue 是用於儲存一組bean屬性的信息和值的對像。

  1. public MutablePropertyValues addPropertyValue(PropertyValue pv) {
  2.  for (int i = 0; i < this.propertyValueList.size(); i++) {
  3.   PropertyValue currentPv = this.propertyValueList.get(i);
  4.   if (currentPv.getName().equals(pv.getName())) {
  5.    pv = mergeIfRequired(pv, currentPv);
  6.    setPropertyValueAt(pv, i);
  7.    return this;
  8.   }
  9.  }
  10.  this.propertyValueList.add(pv);
  11.  return this;
  12. }

添加 PropertyValue 物件,替換或者合併相同的屬性值。整個過程其實與上面猜測相差不是很大。至此,PropertyOverrideConfigurer 到這裡也就分析完畢了。最後看下 PropertyPlaceholderConfigurer 和 PropertyOverrideConfigurer 整體的結構圖:

赞(0)

分享創造快樂