1. 程式人生 > >曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)

曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)

寫在前面的話

相關背景及資源:

曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享

曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解

曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下

曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?

曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean

曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的

曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)

曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)

工程程式碼地址 思維導圖地址

工程結構圖:

概要

先給大家看看spring支援的xml配置,我列了個表格如下:

namespace element
util constant、property-path、list、set、map、properties
context property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server
beans import、bean、alias
task annotation-driven、scheduler、scheduled-tasks、executor
cache advice、annotation-driven
aop config、scoped-proxy、aspectj-autoproxy

我題目的意思是,spring在解析每個不同的xml元素時,其實是有共性的。所有這些元素的解析器,都實現了BeanDefinitionParser。這個介面只有一個方法,作用就是解析元素時,根據元素的配置,來收集beanDefinition,正所謂:條條大道通羅馬,各種xml配置元素,各種註解配置,就是那些大道,羅馬是什麼?

就是beanDefinition

從第一篇到現在,已經第9篇了,我們還在講bean definition,其實就是因為,只有深刻地理解了它,後面才能更方便地理解spring boot,理解configuration註解,理解enable,理解自動裝配。

好了,切入本篇,本篇要講解的xml元素是context名稱空間裡的。

context:property-placeholder

用法

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath*:application.properties"/>

    <bean class="org.springframework.contextnamespace.TestPropertiesVO">
        <property name="name" value="${name}"/>
    </bean>
</beans>
@Data
public class TestPropertiesVO {

    private String name;
}   
#application.properties
name: Phil

測試程式碼:

package org.springframework.contextnamespace;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.MyFastJson;

import java.util.List;
import java.util.Map;

@Slf4j
public class TestPropertyPlaceholder {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                new String[]{"classpath:context-namespace-test-property-holder.xml"},false);
        context.refresh();

        Map<String, Object> map = context.getDefaultListableBeanFactory().getAllSingletonObjectMap();
        log.info("singletons:{}", JSONObject.toJSONString(map));

        List<BeanDefinition> list =
                context.getBeanFactory().getBeanDefinitionList();
        MyFastJson.printJsonStringForBeanDefinitionList(list);
        // 獲取該bean,列印
        Object bean = context.getBean(TestPropertiesVO.class);
        System.out.println("bean:" + bean);

    }
}

輸出如下:

bean:TestPropertiesVO(name=Phil)

如果我們修改xml:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context.xsd">
    //註釋之,看看會怎樣
    <!--<context:property-placeholder location="classpath*:application.properties"/>-->

    <bean class="org.springframework.contextnamespace.TestPropertiesVO">
        <property name="name" value="${name}"/>
    </bean>
</beans>

輸出如下:

bean:TestPropertiesVO(name=${name})

可以看到,這樣子呢,就沒法解析到properties中的值了。

等價用法

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context.xsd">

    <!--<context:property-placeholder location="classpath*:application.properties"/>-->
    
    // 這個配置方式,和上面那個,效果其實是一樣的;上面那個,是對下邊這種的封裝
    <bean id="propertyPlaceholderConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:application.properties</value>
            </list>
        </property>
    </bean>

    <bean class="org.springframework.contextnamespace.TestPropertiesVO">
        <property name="name" value="${name}"/>
    </bean>
</beans>

元素解析

我們切入到org.springframework.context.config.ContextNamespaceHandler,查詢下該元素的解析器。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }

}

我們可以看到,本元素的解析器是:PropertyPlaceholderBeanDefinitionParser

先看看類繼承結構:

大家注意第三層,類名裡,有Single字樣,說明了它是單身狗?不是。說明這個xml元素解析器,最終只得到一個bean definition。

第四層的AbstractPropertyLoadingBeanDefinitionParser,就是提供一個抽象類,提取一些對應的解析器中公共的方法。

可以簡單一看:

abstract class AbstractPropertyLoadingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

   @Override
   protected boolean shouldGenerateId() {
      return true;
   }
   
   // 獲取一些屬性
   @Override
   protected void doParse(Element element, BeanDefinitionBuilder builder) {
      String location = element.getAttribute("location");
      if (StringUtils.hasLength(location)) {
         String[] locations = StringUtils.commaDelimitedListToStringArray(location);
         builder.addPropertyValue("locations", locations);
      }

      String propertiesRef = element.getAttribute("properties-ref");
      if (StringUtils.hasLength(propertiesRef)) {
         builder.addPropertyReference("properties", propertiesRef);
      }

      String fileEncoding = element.getAttribute("file-encoding");
      if (StringUtils.hasLength(fileEncoding)) {
         builder.addPropertyValue("fileEncoding", fileEncoding);
      }

      String order = element.getAttribute("order");
      if (StringUtils.hasLength(order)) {
         builder.addPropertyValue("order", Integer.valueOf(order));
      }

      builder.addPropertyValue("ignoreResourceNotFound",
            Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));

      builder.addPropertyValue("localOverride",
            Boolean.valueOf(element.getAttribute("local-override")));
    
      builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
   }

}

看了父,不看正主也說不過去,這裡呢,正主是真的簡單:

class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {

   private static final String SYSTEM_PROPERTIES_MODE_ATTRIB = "system-properties-mode";
   private static final String SYSTEM_PROPERTIES_MODE_DEFAULT = "ENVIRONMENT";
   
   // 這裡獲取bean的class,注意,這裡的class,是不是和前面:等價用法那一節裡,配置的bean的class一樣
   // 所以啊,context:property-placeholder和等價用法裡的底層實現,還是一樣的
   @Override
   protected Class<?> getBeanClass(Element element) {
      ...
      return PropertyPlaceholderConfigurer.class;
   }

   @Override
   protected void doParse(Element element, BeanDefinitionBuilder builder) {
      super.doParse(element, builder);

      builder.addPropertyValue("ignoreUnresolvablePlaceholders",
            Boolean.valueOf(element.getAttribute("ignore-unresolvable")));

      String systemPropertiesModeName = element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIB);
      if (StringUtils.hasLength(systemPropertiesModeName) &&
            !systemPropertiesModeName.equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
         builder.addPropertyValue("systemPropertiesModeName", "SYSTEM_PROPERTIES_MODE_"+systemPropertiesModeName);
      }
   }

}

大家可以看註釋,這裡返回的class,和等價用法裡的的class是一模一樣。說明了什麼呢?大家這麼聰明,不用我多說了。

這個class,PropertyPlaceholderConfigurer,其實還是比較特別的,我們看看其類圖:

這裡,我們發現這個bean class,竟然是一個BeanFactoryPostProcessor。這個介面有什麼作用呢,大概就是,等所有的beanDefinition都裝載了之後,會呼叫實現了BeanFactoryPostProcessor介面的bean,對beanDefinition進行處理。

如果對這塊感興趣,可以看博主之前的一篇文章,網上也很多解析,可自行搜尋:

曹工雜談:為什麼很少需要改Spring原始碼,因為擴充套件點太多了,說說Spring的後置處理器

context:property-override

用法

這個元素,一般比較少用,但今天查了一下,我覺得這個還比較有意思,而且很奇妙地和當前spring boot外部化配置的思想吻合。

它的用途說起來比較晦澀,我們看例子就知道了:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context.xsd">


    <bean id="person" class="org.springframework.contextnamespace.Person" >
        <property name="name" value="Ram"/>
        <property name="age" value="20"/>
        <property name="location" value="Varanasi"/>
    </bean>
</beans>
package org.springframework.contextnamespace;

import lombok.Data;

@Data
public class Person {
    private String name;
    private int age;
    private String location;

}

測試程式碼:

package org.springframework.contextnamespace;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.MyFastJson;

import java.util.List;
import java.util.Map;

/**
 * desc:
 *
 * @author : caokunliang
 * creat_date: 2019/12/25 0025
 * creat_time: 15:50
 **/
@Slf4j
public class TestPropertyOverride {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                new String[]{"classpath:context-namespace-test-property-override.xml"},false);
        context.refresh();

        // 獲取bean
        Object bean = context.getBean(Person.class);
        System.out.println("bean:" + bean);

    }
}

輸出如下:

bean:Person(name=Ram, age=20, location=Varanasi)

這個應該大家都懂。

接下來,我們在xml裡定義一個元素:

    <?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context.xsd">
    // 配置了這個玩意
    <context:property-override location="classpath:beanOverride.properties"/>

    <bean id="person" class="org.springframework.contextnamespace.Person" >
        <property name="name" value="Ram"/>
        <property name="age" value="20"/>
        <property name="location" value="Varanasi"/>
    </bean>
</beans>
#beanOverride.properties 
person.age=40
person.location=Delhi

測試程式不變,這次的輸出如下:

bean:Person(name=Ram, age=40, location=Delhi)

也就是說,外部配置檔案:beanOverride.properties中的屬性,覆蓋了xml中的bean的屬性。

而現在,spring boot的environment解析變數時,也是外部的配置檔案、命令列引數、環境變數等,優先順序高於jar包內的配置,是不是和我們這個元素的作用比較像呢?

等價用法

如果不使用:,也可以像下面這樣使用:

<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
    <property name="location" value="classpath:beanOverride.properties" />
</bean> 

元素解析

ContextNamespaceHandler,我們可以找到該元素對應的parser:PropertyOverrideBeanDefinitionParser

public void init() {
   registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
   registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
   registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
   registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
   registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
   registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
   registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
   registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

類實現也很簡單,和前面的一樣,都繼承了同一個基類:AbstractPropertyLoadingBeanDefinitionParser

簡單看看其實現吧:

class PropertyOverrideBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser {

    @Override
    protected Class getBeanClass(Element element) {
        return PropertyOverrideConfigurer.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {

        super.doParse(element, builder);
        builder.addPropertyValue("ignoreInvalidKeys",
                Boolean.valueOf(element.getAttribute("ignore-unresolvable")));

    }

}

這裡,看看我們獲得的bean class:

和前面討論的一樣,也是一個BeanFactoryPostProcessor

總結

又需要回答題目的問題了,從xml檔案裡,解析得到了什麼呢,答案依然是beanDefinition。

不過呢,這次的beanClass,略有不同,因為他們是特殊的class,是可以參與beanDefinition生命週期的class,

因為他們實現了BeanFactoryPostProcessor

大家可以再看看前面util名稱空間,那些bean class呢,主要就是FactoryBean

本篇原始碼位置:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo/src/main/java/org/springframework/contextnamespace

由於context名稱空間都是些大人物,所以本篇主要是先給大家熱身,下一講,我們講講這裡面的:

annotation-config、component-scan

我簡單看了兩眼,還挺有意思,歡迎大家和我一起學習