在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法中,遍历每一个节点,判断是否为默认命名空间中的节点,如果是非默认命名空间的,调用delegate.parseCustomElement(ele)方法进行处理。在学习自定义标签解析之前,先写一个自定义标签的demo。

一、自定义标签示例

  • 定义POJO

下面定义一个Person类

package com.demo.beans.custom;

/**
 *@author zhzhd
 *@date 2018/6/11
 *@package 
 *@describe
 **/
public class Person {
    private String userName;
    private String sex;
    private Integer age;

    public Person(String name){
        this.userName = name;
    }
   ......省略setter和getter
}
  • 定义一个文件描述组件

在webapp下创建person.xsd文件,内容如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.zhzhd.com/schema/person"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified"
            targetNamespace="http://www.zhzhd.com/schema/person">
    <xsd:complexType name="person">
        <xsd:attribute name="id" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation>
                    <![CDATA[ The unique identifier for a bean. ]]>
                </xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="userName" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation>
                    <![CDATA[ The userName for a bean. ]]>
                </xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="sex" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation>
                    <![CDATA[ The sex of the bean. ]]>
                </xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="age" type="xsd:integer">
            <xsd:annotation>
                <xsd:documentation>
                    <![CDATA[ The age of the bean. ]]>
                </xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
    </xsd:complexType>

    <xsd:element name="person" type="person">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ The service config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>
</xsd:schema>
  • 自定义NamespaceHandler

自定义MyNamespaceHandler,继承NamespaceHandlerSupport,并且重写init()方法,实现如下:

package com.demo.beans.custom;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 *@author zhzhd
 *@date 2018/6/11
 *@package com.demo.beans.custom
 *@describe
 **/
public class MyNamespaceHandler extends NamespaceHandlerSupport{
    public void init() {
        registerBeanDefinitionParser("", new PersonBeanDefinitionParser());
    }
}
  • 实现自定义PersonBeanDefinitionParser

自定义实现PersonBeanDefinitionParser,并且重写Class getBeanClass(Element element)和doParse(Element element, BeanDefinitionBuilder bean)方法。getBeanClass方法返回当前bean的class,doParse解析自定义元素属性,实现如下:

package com.demo.beans.custom;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
 *@author zhzhd
 *@date 2018/6/11
 *@package com.demo.beans.custom
 *@describe
 **/
public class PersonBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{

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

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder bean){
        String name = element.getAttribute("name");
        bean.addConstructorArgValue(name);
        if (StringUtils.hasText(name)){
            bean.addConstructorArgValue(name);
        }

        String age = element.getAttribute("age");
        String sex = element.getAttribute("sex");
        if (StringUtils.hasText(age)){
            bean.addPropertyValue("age", Integer.parseInt(age));
        }
        if (StringUtils.hasText(age)){
            bean.addPropertyValue("sex", sex);
        }
    }
}
  • 创建spring.handlers和spring.schemas
http\://www.zhzhd.com/schema/person=com.demo.beans.custom.MyNamespaceHandler
http\://www.zhzhd.com/schema/person.xsd=person.xsd
  • 在XML中配置bean以及测试

接下来,需要在spring的配置文件中加入命名空间信息,并且配置自定义bean,实现如下:

<?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:custom="http://www.zhzhd.com/schema/person"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.zhzhd.com/schema/person
       http://www.zhzhd.com/schema/person.xsd">
    <custom:person userName="zhzhd" sex="男" age="18" id="testPerson"></custom:person>
</beans>

测试demo如下:

    public static void main(String[] args) {
        BeanFactory beanFactory1 = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        ApplicationContext beanFactory = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"},true);
        Person person = beanFactory.getBean("testPerson", Person.class);
        System.out.println(JSON.toJSONString(person));
    }

从上面的示例可以看到,我们定义了自定义节点的handler,spring在解析xml中节点或属性的时候,当遇到自定义节点和属性时,会调用响应的handler进行解析,下面具体分析自定义节点和属性解析的源码。

二、spring解析自定义标签的源码分析

首先,从xml元素的解析开始分析,在parseBeanDefinitions()方法中,判断如果是xml中节点或属性是自定义的,则调用BeanDefinitionParserDelegate的parseCustomElement()方法处理,下面是parseCustomElement()的实现:

    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        // 读取命名空间url
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        // 解析命名空间,返回NamespaceHandler实例
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        // 解析自定义标签
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

从Element中获取到自定义命名空间uri后,交给DefaultNamespaceHandlerResolver的resolve()方法解析并实例化NamespaceHandler实例,具体实现如下:

public NamespaceHandler resolve(String namespaceUri) {
        // 获取命名空间uri和handler类名关系map
        Map<String, Object> handlerMappings = getHandlerMappings();
        // 获取类名
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            String className = (String) handlerOrClassName;
            try {
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                // 实例化NamespaceHandler
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                // 初始化NamespaceHandler
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                        "] for namespace [" + namespaceUri + "]", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                        className + "] for namespace [" + namespaceUri + "]", err);
            }
        }
    }

zhzhd
16 声望5 粉丝