曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

写在前面的话

相关背景及资源:

曹工说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支持的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元素,比较简单,比如上一篇提到的util:constant,只能得到一个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

具体的解析过程,和上一讲里的util:constant相似,这里只说不同的:

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 从这个util:list元素获得了什么,一个工厂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源码,有问题欢迎一起交流!

posted @ 2020-01-08 23:45  三国梦回  阅读(510)  评论(0编辑  收藏