[Spring] 深度解析系列 (2 )- xml读取和标签解析

概述

在上一篇文章中,讲述了定义在classpath 下的文件,spring是如何加载到内存,并转化为流的过程,那么这篇文章就从xml转化为document 对象开始。
在开始之前,有几个类要先介绍一下,这是在上篇文章中涉及到的,对后续的流程也至关重要。

核心类
  • DefaultListableBeanFactory
    该类是整个Bean加载的核心,是spring注册和加载bean的默认实现,类图如下:


    [Spring] 深度解析系列 (2 )- xml读取和标签解析_第1张图片
    类图.png

    挑几个比较重要的子类说一下:

    • AliasRegistry: 定义对alias(别名)的增删该操作
    • BeanDefinitionRegistry : 定义对BeanDefinition的各种增删改操作
    • SingletornBeanRegistry: 定义了对单例的注册和获取
    • AutowireCapableBeanFactory:提供创建bean, 自动注入,初始化以及应用bean的后处理器
    • AbstractAutowireCapableBeanFactory: 综合AbstractBeanFactory 并对 Capable BeanFactory进行实现。
    • ConfigurableListableBeanFactory : 配置的清单
    • DefaultListableBeanFactory: 综合上面所有的功能,主要是对bean 注册后的处理。
  • XmlBeanDefinitionReader
    xml配置文件读取的是Spring中重要的内容,Spring的大部分功能都是以配置作为切入点的。就从该类来梳理一下文件读取,解析,注册的大致脉络。

    • ResourceLoader: 定义资源加载器
    • BeanDefinitionReader: 主要定义资源文件读取并转换为BeanDefinition的各个功能
    • BeanDefinitionDocumentReader : 定义读取Document并注册BeanDefinition功能
    • BeanDefinitionParserDelegate : 定义解析Element的各种方法

上篇文章分析到此处,本篇便从此开始:


[Spring] 深度解析系列 (2 )- xml读取和标签解析_第2张图片
xml转化为流.png

进入doLoadBeanDefinitions()方法 :

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {
        try {
            //转化为Document 对象
            Document doc = doLoadDocument(inputSource, resource);
            //启动对Bean定义解析的详细过程,会用到Spring Bean的配置规则
            return registerBeanDefinitions(doc, resource);
        }
        //删除了部分catch语句
        catch (....) {
            throw ex;
        }
    }

看一下 doLoadDocument()方法:


doLoadDocument.png

这里将getEntityResolver()和getValidationModeForResource()展开说一下,说之前先引入两个概念,XML文件的验证模式:

  • DTD: 文档类型定义
  • XSD: XMLSchema , 描述XML文档是否符合其要求。
    Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD .
    程序继续往下走,


    [Spring] 深度解析系列 (2 )- xml读取和标签解析_第3张图片
    image.png

此处的加载跳转到了DefaultDocumentLoader 类中的loadDocument()方法,在该方法中有一个EntityResolver参数, 来说下这个参数的作用。
官网中是这样解释的,如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动注册一个实例。
看下createDocumentBuilderFactory() 方法:


[Spring] 深度解析系列 (2 )- xml读取和标签解析_第4张图片
image.png

[Spring] 深度解析系列 (2 )- xml读取和标签解析_第5张图片
image.png

上面的代码没有啥特别的,我直接罗列了,通过SAX解析XML文档的套路差不多,都是先创建DocumentBuidlerFactory,在创建DocumentBuilder,进而解析inputScource来返回Document对象。

解析XML的方式: Dom | Sax | Dom4j | JDom
这里要说的是,Spring解析Xml采用的是Dom,而这种解析方式,是将文件全部加载到内存,当xml文件较大时,对内存耗费比较大,容易影响解析性能并造成内存溢出。

至此,Spring IOC容器根据定位的Bean定义资源文件,并将其加载读入转换为document对象的过程完成。
下面继续分析:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //创建DocumentReader来对XML格式的BeanDefinition进行解析    
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //获得容器中已经存在的Bean数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    //具体的解析过程在这个方法中执行
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

createBeanDefinitionDocumentReader()方法返回的是DefaultDefinitionDocumentReader对象。进入DefaultDefinitionDocumentReader 后发现这个方法的重要目的就是提取root ,以便于再次将root作为参数,继续BeanDefinition的注册。此处便进入了解析的核心部分。
Bean定义资源的解析分为以下两个过程:

  • 通过调用xml解析器,将资源定义文件转换为Document对象,document对应并没有按照spring bean的规则进行解析。
  • 在完成通用xml解析之后,按照Spring的Bean规则对Document对象进行解析,这个过程是在documentReader中实现的。具体的操作是由- -DefaultBeanDefinitionDocumentReader完成的。
    处理的结果由BeanDefinitionHolder对象持有。解析过程由BeanDefinitionParserDelegate来实现
//根据Spring DTD 对bean定义的规则解析Bean定义Document对象
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    //获取xml描述符
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    //获得Document的根对象
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    //BeanDefinitionParserDelegate中定义了Spring Bean定义的XML文件的各种元素
    //默认BeanDefinitionParserDelegate会处理”http://www.springframework.org/schema/beans“命名空间下元素及其属性
    this.delegate = createDelegate(getReaderContext(), root, parent);
    
    if (this.delegate.isDefaultNamespace(root)) {
        //对于默认的命名空间,首先开始的是对profile属性解析
        //profile用得最多的是DataSource在不同环境下使用不同的bean
        //spring使用StringTokenizer来进行字符串的分割,但是jdk为了兼容性是推荐使用String.split()方法的:
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());
                    }
                    return;
            }
        }
    }
    //接下来的解析使用了模板模式
    //进行自定义的解析,增强解析过程的可扩展性 ,空实现
    preProcessXml(root);
    //从Document的根元素开始进行Bean定义的document对象
    parseBeanDefinitions(root, this.delegate);
    //在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性。空实现
    postProcessXml(root);
    this.delegate = parent;
}
protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
    BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
    delegate.initDefaults(root, parentDelegate);
    return delegate;
}

解析document文件,不同的命名的空间采用不同的方法处理

protected void parseBeanDefinitions(Element root,BeanDefinitionParserDelegate delegate) {
    //Bean定义的Document对象使用了Spring默认的XML命名空间
    if (delegate.isDefaultNamespace(root)) {
        //获取Document对象的所有子节点,NodeList的含义上面进行了介绍
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);  //获取Node节点
            //判断Node是否是Element类型
            if (node instanceof Element) {
                Element ele = (Element) node;
                //判断该元素是否属于Spring定义Bean的默认命名空间
                if (delegate.isDefaultNamespace(ele)) {
                    //使用Spring的Bean规则解析元素节点
                    parseDefaultElement(ele, delegate);
                }
                else {
                    //使用用户自定义的规则进行解析
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        //没有使用spring默认的命名空间,则使用用户自定义的解析规则解析
        delegate.parseCustomElement(root);
    }
}

使用spring的Bean规则解析Document元素节点,有些元素节点是 等,则分别进行解析

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    //如果元素节点是导入元素,进行导入解析
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    //如果元素节点是别名元素,进行别名解析
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    //如果是转入此流程处理
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    //如果是转入此流程处理
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
        doRegisterBeanDefinitions(ele);
    }
}

在4中标签中对bean标签的解析也最为复杂也最为重要,主要看一下对bean标签的解析过程。

protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate) {
    //BeanDefinitionHolder是对BeanDefinition的封装,delegate解析完成后使用holder封装,bdHolder 包含了id ,别名和BeanDefinition的信息
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            //向容器注册封装后的实例
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            ....
        }
        //在Beandefinition向IoC容器注册完成以后,发送消息
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

下面看一下parseBeanDefinitionElement()方法的具体实现,对于BeanDefinition的注册时存放在ConcurrentHashMap中的,beanName变为存放的健,过程分为4个部分:

  • 委托BeanDefinitionDelegate类的parseBeanDefinitionElement()方法进行解析,返回BeanDefinitionHolder ,经过这个方法后,bdHolder实例已经包含配置中 的各种基本属性,例如 class , name , id 等
  • 当返回的Holder不为空,若存在默认标签的情况下在有自定义的属性,还需再次对自定义的标签进行解析
  • 解析完成后,需要向DefaultListableBeanFactory进行注册,注册操作委托给了BeanDefinitionReaderUtils类
  • 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。

下面看具体的实现。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
    //获取id的值
    String id = ele.getAttribute(ID_ATTRIBUTE);
    //获取name的值
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    //分割name属性
    List aliases = new ArrayList();
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
    }
    //将id赋值给beanName 
    String beanName = id;
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        beanName = aliases.remove(0);
        if (logger.isDebugEnabled()) {
            logger.debug("....");
        }
    }

    if (containingBean == null) {
        checkNameUniqueness(beanName, aliases, ele);
    }
    //该方法引发对Bean元素的详细解析
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    //程序执行到此处,整个标签的解析就算结束了。一个beanDefinition就创建好了
    if (beanDefinition != null) {
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    //如果不存在beanName,那么根据Spring中的提供的命名规则为当前bean生成对应的beanName
                    beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                }
                else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("....");
                }
            }
            catch (Exception ex) {
                    error(ex.getMessage(), ele);
                    return null;
                }
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            //将信息封装到BeanDefinitionHolder中
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }
    return null;
}

BeanDefinition可以看成是对定义的抽象,这个数据对象中封装的数据大多都是与定义相关的,也有很多就是我们在定义Bean时看到的那些Spring标记。这个BeanDefinition数据类型是非常重要的,它封装了很多基本数据,这些都是Ioc容器需要的,上面代码最后我们返回了一个BeanDefinitionHolder实例,这个实例封装了beanDefinition,beanName, aliase三个信息,beanDefinition中也包含了beanName,aliase信息。

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {

    this.parseState.push(new BeanEntry(beanName));
    //这里只读取定义的中设置的class名字,然后载入到BeanDefinition中去,只是做个记录,并不涉及对象的实例化过程,对象的实例化过程实际是在依赖注入时完成的
    String className = null;
    if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
        className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
    }

    try {
        //解析parent属性
        String parent = null;
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }

        //创建用于承载属性的AbstractBeanDefinition类型的GenereicBeanDefinition
        //这里生成需要的BeanDefinition对象,为Bean定义的信息做载入做准备
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);
        //这里对当前Bean元素进行属性解析,并设置description信息
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
        //解析元数据
        parseMetaElements(ele, bd);
        //解析lookup-method属性
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        //解析replaced-method属性
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        //解析构造函数参数
        parseConstructorArgElements(ele, bd);
        //解析property子元素
        parsePropertyElements(ele, bd);
        //解析qualifier子元素
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));
        //返回BeanDefinition对象
        return bd;
    } catch (Throwable ex) {
        ...
    }
    finally {
        this.parseState.pop();
    }
    return null;
}

从上面的代码可以看出,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例,而代码createBeanDefinition(className,parent)的作用就是实现此功能。创建完承接的实例后,便可以进行各种属性的解析了,首先进行解析的是在标签中定义的各种属性,如scope, singleton,abstract,lazy-init等,然后再解析子标签中的属性,如:lookup-method ,constructor-arg等。解析完成之后关联到实例上,之所以能进行关联,是因为xml中所有的配置都能在GenericBeanDefinition的实力类中找到对应的配置。 此时容器还没有起作用,要想起作用,需要向容器进行注册。

分析到这,已经完成了xml文件向BeanDefinition的转化,每个一个标签都会转化成一个BeanDefinition对应的实体。实体中包含了中定义的所有属性。

上面都是默认标签的解析,spring其实还支持自定义标签的解析,这里就不再赘述了,感兴趣的可以查看一下dubbo是如何解析的。大致包含如下几个步骤:

  • 创建一个需要扩展的组件
  • 定义一个xsd文件描述组件的内容
  • 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
  • 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
  • 编写Spring.handlers 和 Spring.schemas文件。

最后以一张图来结束本篇文章


[Spring] 深度解析系列 (2 )- xml读取和标签解析_第6张图片
微信图片_20191119223931.jpg

下一篇文章, bean的载入

你可能感兴趣的:([Spring] 深度解析系列 (2 )- xml读取和标签解析)