Spring源码分析-自定义标签解析

从XML配置文件到Document的转换完成后,就开始解析各种元素了,解析需要区分默认标签和自定义标签。本文介绍自定义标签的解析。
解析过程和用法息息相关,如果不了解自定义标签的使用,那么解析过程中的一些步骤就会疑惑。所以,先介绍如何使用自定义标签。

使用自定义标签

1.创建一个需要扩展的组件。如下创建了一个普通的POJO,用来接收配置参数。

public class User {
    private String userName;
    private String email;
    // setXxx、getXxx
}

2.定义一个XSD文件描述组件


<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lexueba.com/schema/user"
        xmlns:tns="http://www.lexueba.com/schema/user"
        elementFormDefault="qualified">
    <element name="user">
        <complexType>
            <attribute name="userName" type="string"/>
            <attribute name="email" type="string"/>
        complexType>
    element>
schema>

这是一个Schema文件,它的作用是定义XML文件的合法结构,是DTD的替代者。

xmlns="http://www.w3.org/2001/XMLSchema"

表示 schema 中用到的元素和数据类型来自命名空间”http://www.w3.org/2001/XMLSchema”

elementFormDefault="qualified"

指出任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定。
//targetNameSpace?
3.实现BeanDefinitionParser接口,定义如何解析XSD文件。

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    // Element对应的类
    protected Class getBeanClass(Element element){
        return User.class;
    }

    //从element中提取并解析对应的元素
    protected void doParse(Element element, BeanDefinitionBuilder bean){
        String userName = element.getAttribute("userName");
        String email = element.getAttribute("email");
        if(StringUtils.hasText(userName)){
            bean.addPropertyValue("userName", userName);
        }
        if(StringUtils.hasText(email)){
            bean.addPropertyValue("emai", email);
        }
    }
}

4.扩展NamespaceHandlerSupport,目的是将组件注册到Spring容器。

public class MyNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        // 意味着当遇到自定义标签这样类似于以user开头的元素,
        // 就会把这个元素扔给对应的UserBeanDefinitionParser去解析
        registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
    }
}

5.修改Spring.handlers

http\:www.lexueba.com/schema/user=com.tony.MyNamespaceHandler

6.修改Spring.schemas

http\://www.lexueba.com/schema/user.xsd=META-INF/Spring-test.xsd

自定义标签解析

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        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));
    }

这段代码的思路很清晰:
1.根据对应的bean获取对应的命名空间;
2.根据命名空间解析的对应的处理器;
3.根据用户自定义的处理器进行解析;

下面展开阐述。

获取命名空间

org.w3c.dom.Node中提供了获取命名空间的方法:

public String getNamespaceURI(Node node){
    return node.getNamespaceURI();
}

提取自定义标签处理器

public NamespaceHandler resolve(String namespaceUri) {
    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;

        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) BeanUtils.instantiateClass(handlerClass);
        namespaceHandler.init();
        handlerMappings.put(namespaceUri, namespaceHandler);
        return namespaceHandler;
    }
}

1.getHandlerMappings()
使用自定义标签,会在/META-INF/文件夹下Spring.handlers中设置命名空间和命名空间处理器的映射关系,比如:

http\://www.abc.com/schema/user=com.tony.MyNamespaceHandler

getHandlerMappings()方法读取Spring.handlers,将这种映射关系缓存在ConcurrentHashMap中。
2.DefaultNamespaceHandlerResolver
1)根据命名空间从缓存map中尝试获取;
2)如果可以获取到,且没有做过解析,得到的是类路径,使用反射将类路径转化为类;
3) 根据class初始化实例,调用init()方法进行BeanDefinitionParser的注册;
4)将实例化好的NamespaceHandler放置到handlerMappings中,返回该实例;

标签解析

父类NamespaceHandlerSupprt的parse方法:

public BeanDefinition parse(Element element, ParserContext parserContext){
    return findParserForElement(element,parserContext).parse(element,parserContext);
}

1.寻找解析器
在自定义NamespaceHandler执行init()方法时,已经将自定义标签名和解析器注册到parsers中了,所以这里先获取自定义标签名,然后到parsers中找就行;

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    //获取元素名称,也就是中的user
    String localName = parserContext.getDelegate().getLocalName(element);
    // 根据user找到对应的解析器,就是在registerBeanDefinitionParser("user",new UserBeanDefinitionParser())注册的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

2.parseInternal

AbstractBeanDefinition definition = parseInternal(element, parserContext);

真正的解析落在这一句上,parseInternal方法中,在经过对beanClass、scope、lazyInit等属性准备后,最终将解析工作委托给我们自定义的MyNamespaceHandler的doParse方法上。

你可能感兴趣的:(spring源码分析)