曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)

寫在前面的話

相關背景及資源:html

曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java

曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git

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

曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?json

曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanapp

曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的ide

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)spring-boot

工程代碼地址 思惟導圖地址學習

工程結構圖:測試

概要

先給你們看看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配置,那解析這些xml的代碼,是不是有共性的呢?仍是說,就是很隨意的,產品經理說要支持這個元素的解析,就寫個分支呢?

看過我上講的同窗應該知道,無論是什麼元素,無論在哪一個namespace下,其對應的解析代碼,都是一種類,這種類,叫作:BeanDefinitionParser,這個類的接口以下:

org.springframework.beans.factory.xml.BeanDefinitionParser
public interface BeanDefinitionParser {

    /**
     * 解析指定額element,註冊其返回的BeanDefinition到BeanDefinitionRegistry
     * (使用參數ParserContext#getRegistry()獲得BeanDefinitionRegistry)
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

}

這個接口的實現類,至關多,除了beans命名空間下的xml元素,其餘namespace下的xml元素的解析代碼都實現了這個接口。

首先是util命名空間下:

其次是context命名空間:

這裏面有你們熟悉的

這裏就不一一列舉了,因此你們知道了,每一個xml元素的解析器,都是實現了BeanDefinitionParser,這個接口的方法,就是交給各個子類去實現:針對指定的xml元素,如何獲取到對應的bean definition

有的xml元素,比較簡單,好比上一篇提到的 ,只能獲得一個 bean definition(factory bean);還有的xml元素,則是羣攻魔法,好比<context:component-scan>這種,一把就能撈一大波 bean definition上來。

本講,咱們會繼續從util namespace開始,將比較常見的xml元素,一路掃過去

util:properties

用法以下:

#test.properties
name=xxx system
import lombok.Data;

@Data
public class TestPropertiesBean {
    private String appName;
}
spring xml中以下配置:
    <util:properties id="properties"
                     location="classpath:test.properties"/>

    <bean class="org.springframework.utilnamespace.TestPropertiesBean">
        // 注意,這裏的value,#{properties.name},點號前面引用了上面的properties bean的id,點號後面
        // 是properties文件裏key的名稱
        <property name="appName" value="#{properties.name}"></property>
    </bean>

測試類以下:

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

        List<BeanDefinition> list =
                context.getBeanFactory().getBeanDefinitionList();
        MyFastJson.printJsonStringForBeanDefinitionList(list);

        Object o = context.getBean(TestPropertiesBean.class);
        System.out.println(o);
    }
}

輸出以下:

TestPropertiesBean(appName=xxx system)

原理解析

UtilNamespaceHandler中,咱們看看該元素對應的BeanDefinitionParser是啥:

public class UtilNamespaceHandler extends NamespaceHandlerSupport {


    public void init() {
        registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
        registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
        registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
        registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
        registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
        registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
    }
}

ok! 是PropertiesBeanDefinitionParser

具體的解析過程,和上一講裏的 類似,這裏只說不一樣的:

private static class PropertiesBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
        
         // 這裏就是指定了bean definition裏的bean class
        @Override
        protected Class getBeanClass(Element element) {
            return PropertiesFactoryBean.class;
        }
        
        // 一些定製邏輯,無需操心
        @Override
        protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
            super.doParse(element, parserContext, builder);
            Properties parsedProps = parserContext.getDelegate().parsePropsElement(element);
            builder.addPropertyValue("properties", parsedProps);
            String scope = element.getAttribute(SCOPE_ATTRIBUTE);
            if (StringUtils.hasLength(scope)) {
                builder.setScope(scope);
            }
        }
    }

這裏其實,主要就是指定了beanClass,其餘邏輯都不甚重要。這裏的beanClass就是PropertiesFactoryBean,類型是一個工廠bean。

由於咱們的主題是,Spring解析xml文件,從中獲得了什麼,因此咱們不會進一步剖析實現,從對上面這個元素的解析來講,就是獲得了一個工廠bean

util:list

用法以下:

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

    <util:list id="testList" list-class="java.util.ArrayList">
        <value>a</value>
        <value>b</value>
        <value>c</value>
    </util:list>

    <bean id="testPropertiesBeanA" class="org.springframework.utilnamespace.TestPropertiesBean">
        <property name="appName" value="xxx"/>
    </bean>
    <bean id="testPropertiesBeanB" class="org.springframework.utilnamespace.TestPropertiesBean">
        <property name="appName" value="yyy"/>
    </bean>
    <util:list id="testBeanList" list-class="java.util.ArrayList">
        <ref bean="testPropertiesBeanA"/>
        <ref bean="testPropertiesBeanB"/>
    </util:list>


</beans>

測試代碼:

@Slf4j
public class TestUtilListElement {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                new String[]{"classpath:util-namespace-test-list.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);
        
        Object bean = context.getBean("testList");
        System.out.println("bean:" + bean);

        bean = context.getBean("testBeanList");
        System.out.println("bean:" + bean);

    }
}

輸出以下:

23:32:06.396 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testList'
bean:[a, b, c]
23:32:06.396 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testBeanList'
bean:[TestPropertiesBean(appName=xxx), TestPropertiesBean(appName=yyy)]

咱們看看這兩個bean的beanDefinitionParser

private static class ListBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
   // 這裏指定本bean的class,能夠看到,這也是一個工廠bean
   @Override
   protected Class getBeanClass(Element element) {
      return ListFactoryBean.class;
   }
    
   //解析元素裏的屬性等
   @Override
   protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
      String listClass = element.getAttribute("list-class");
      List parsedList = parserContext.getDelegate().parseListElement(element, builder.getRawBeanDefinition());
      builder.addPropertyValue("sourceList", parsedList);
      if (StringUtils.hasText(listClass)) {
         builder.addPropertyValue("targetListClass", listClass);
      }
      String scope = element.getAttribute(SCOPE_ATTRIBUTE);
      if (StringUtils.hasLength(scope)) {
         builder.setScope(scope);
      }
   }
}

回到題目,spring 從這個 元素得到了什麼, 一個工廠bean,和前面同樣。

咱們能夠仔細看看beandefinition,我這裏的測試類是用json輸出了的:

{
    "abstract": false,
    "autowireCandidate": true,
    "autowireMode": 0,
    "beanClassName": "org.springframework.beans.factory.config.ListFactoryBean",
    "constructorArgumentValues": {
      "argumentCount": 0,
      "empty": true,
      "genericArgumentValues": [],
      "indexedArgumentValues": {}
    },
    "dependencyCheck": 0,
    "enforceDestroyMethod": true,
    "enforceInitMethod": true,
    "lazyInit": false,
    "lenientConstructorResolution": true,
    "methodOverrides": {
      "empty": true,
      "overrides": []
    },
    "nonPublicAccessAllowed": true,
    "primary": false,
    "propertyValues": {
      "converted": false,
      "empty": false,
      "propertyValueList": [
        {
          "converted": false,
          "name": "sourceList",
          "optional": false,
          "value": [
            {
              "beanName": "testPropertiesBeanA",
              "toParent": false
            },
            {
              "beanName": "testPropertiesBeanB",
              "toParent": false
            }
          ]
        },
        {
          "converted": false,
          "name": "targetListClass",
          "optional": false,
          "value": "java.util.ArrayList"
        }
      ]
    },
    "prototype": false,
    "qualifiers": [],
    "resolvedAutowireMode": 0,
    "role": 0,
    "scope": "",
    "singleton": true,
    "synthetic": false
  }

從上面能夠看出,beanClass是ListFactoryBean,而咱們xml裏配置的元素,則被解析後,存放到了propertyValues,被做爲了這個bean的屬性對待。

總結

util命名空間還有幾個別的元素,好比map、set,都差很少,spring都把它們解析爲了一個工廠bean。

工廠bean和普通bean的差異,會放到後面再說,下一講,會繼續講解context命名空間的元素。

源碼我放在:

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

歡迎你們和我一塊兒學習spring/spring boot源碼,有問題歡迎一塊兒交流!

相關文章
相關標籤/搜索