• 曹工说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源码,有问题欢迎一起交流!

  • 相关阅读:
    dockerk个人学习(0)
    ubuntu编译python源码的坑
    查找大目录
    ubuntu 远程gui显示
    paramiko模块
    python open和file的区别
    python type metaclass
    python 生成器 迭代器 yiled
    博客暂停更新,请移步新主页
    win10禁用自动更新服务
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/12158935.html
Copyright © 2020-2023  润新知