【Spring源码解析】Spring XML配置自定义标签的解析

1、前言

上两篇文章【Spring源码解析】Spring XML配置默认bean标签解析【Spring源码解析】Spring xml配置import、alias、beans标签解析 已经完成了XML方式下对所有默认标签的解析,这篇文章说下自定义标签的解析。

  • 自定义标签

这里说下什么是自定义标签的解析,除了上述的默认标签都是对自定义标签的解析。

主要作用的是对除默认标签外的一些功能扩展,说到扩展,要说默认的标签能不能实现需要扩展的功能?

肯定也是可以的,但是为了更方便,spring推出了扩展的功能。

这个扩展的功能其实不只是我们在用,spring的功能也有使用,例如MVC注解驱动的标签 <mvc:annotation-driven/> 。

2、实例解析

这里我们使用一个自己定义的标签。

2.1、源码初步解析

首先我们先了解下自定义标签的解析。

先看下之前定义的解析默认标签和自定义标签,区别在于是否使用默认标签的命名delegate.isDefaultNamespace(root) :

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();

        for(int i = 0; i < nl.getLength(); ++i) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element)node;
                if (delegate.isDefaultNamespace(ele)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }

}

然后看下解析自定义标签,进入方法parseCustomElement :

@Nullable
public BeanDefinition parseCustomElement(Element ele) {
    return this.parseCustomElement(ele, (BeanDefinition)null);
}

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    String namespaceUri = this.getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    } else {
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }
}

这里一共有三个步骤:

  1. getNamespaceURI 获取自定义空间URI。
  2. this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri) 获取自定义解析器。
  3. handler.parse 通过解析器进行转换成 BeanDefination 。

以上就是自定义标签的解析。然后我们就根据具体的源码,实现具体的自定义解析过程。

2.2、自定义实现

  • 创建自定义Bean对象
package com.fankf.bean;

/**
 * @author fankunfeng
 * @date 2022-07-09 23:30
 */
public class Student {

    private String name;
    private String email;
    
    // set/get 省略
}
  • XSD 文件(解析标签)自定义

位置在resources目录下创建META-INF文件夹,并在META-INF 下创建stu.xsd。

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.fans.com/schema/user"
        xmlns:tns="http://www.fans.com/schema/user"
        elementFormDefault="qualified">

    <element name="user">
        <complexType>
            <attribute name="id" type="string"/>
            <attribute name="name" type="string"/>
            <attribute name="email" type="string"/>
        </complexType>
    </element>
</schema>
  • META-INF 下创建读取映射文件Spring.schemas
    用来查找xsd文件位置。
http://www.fans.com/schema/stu.xsd=META-INF/stu.xsd
  • 解析转换对象
public class StudentBeanDefinationParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class<?> getBeanClass(Element element) {
        return Student.class;
    }
    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String name = element.getAttribute("name");
        String email = element.getAttribute("email");
        // 将所有的数据放入ParserContext 中,等到所有bean解析完成统一放入BeanFactory中
        if (StringUtils.hasText(name))
            builder.addPropertyValue("name", name);
        if (StringUtils.hasText(email))
            builder.addPropertyValue("email", email);
    }

}
  • META-INF下创建文件Spring.handlers
    用来查找handler。
http://www.fans.com/schema/user=com.fankf.spring.StudentNamespaceHandler
  • 进行对象到容器
public class StudentNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 以stu 开始的标签交给 StudentBeanDefinationParser 进行解析
        registerBeanDefinitionParser("user", new StudentBeanDefinationParser());
    }
}
  • XML配置文件bean6.xml
<?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:stu="http://www.fans.com/schema/user"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.fans.com/schema/user http://www.fans.com/schema/stu.xsd">
    <stu:user id="zs" name="zs1" email="123@123.com"/>
</beans>
  • 测试代码
@Test
public void test() {
    BeanFactory context = new XmlBeanFactory(new ClassPathResource("bean6.xml"));
    Student student = (Student) context.getBean("zs");
    System.out.println("【实例使用】" + student.getEmail() + "-" + student.getName());
}

结果: 【实例使用】123@123.com-zs1

以上就完成了自定义标签到解析的过程,我们再次看下解析过程。

2.3、再次进行解析

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    String namespaceUri = this.getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    } else {
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }
}
  • this.getNamespaceURI(ele)

断点调试我们就会发现 this.getNamespaceURI(ele) 返回的就是 我们配置在bean6.xml beans标签中的自定义命名空间 http://www.fans.com/schema/user

  • this.readerContext.getNamespaceHandlerResolver()

然后 this.readerContext.getNamespaceHandlerResolver() 会获取解析器。这里解析器有多个,其中就有我们自定义的 http://www.fans.com/schema/user -> com.fankf.spring.StudentNamespaceHandler
这里也可以看到解析器对应的文件位置META-INF下spring.handlers文件。

  • 解析过程

这里就进行解析,进入DefaultNamespaceHandlerResolver##resolve方法:

@Nullable
public NamespaceHandler resolve(String namespaceUri) {
    Map<String, Object> handlerMappings = this.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");
            } else {
                NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        } catch (ClassNotFoundException var7) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
        } catch (LinkageError var8) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
        }
    }
}

分为几个步骤:

  1. 获取解析对象Object
Map<String, Object> handlerMappings = this.getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
  1. 进行初始化
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass)
  1. init

这个就是我们自定义的方法,作用是把user标签的内容交给StudentBeanDefinationParser 进行解析。
当然我们也可以写多个解析的标签,这里只是示例了一个。

@Override
public void init() {
    // 以stu 开始的标签交给 StudentBeanDefinationParser 进行解析
    registerBeanDefinitionParser("user", new StudentBeanDefinationParser());
}
  1. 放入处理映射缓存
handlerMappings.put(namespaceUri, namespaceHandler);
  • 解析过程 handler.parse
    先创建转换上下文 ParserContext。然后进行解析:
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = this.findParserForElement(element, parserContext);
    return parser != null ? parser.parse(element, parserContext) : null;
}

@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    String localName = parserContext.getDelegate().getLocalName(element);
    BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }

    return parser;
}

findParserForElement 的作用是找到我们自定义的解析对象。
我们看到到 parser.parse(element, parserContext) 继续进入:

@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
    AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
        try {
            String id = this.resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element);
            }

            String[] aliases = null;
            if (this.shouldParseNameAsAliases()) {
                String name = element.getAttribute("name");
                if (StringUtils.hasLength(name)) {
                    aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
            }

            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
            this.registerBeanDefinition(holder, parserContext.getRegistry());
            if (this.shouldFireEvents()) {
                BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                this.postProcessComponentDefinition(componentDefinition);
                parserContext.registerComponent(componentDefinition);
            }
        } catch (BeanDefinitionStoreException var8) {
            String msg = var8.getMessage();
            parserContext.getReaderContext().error(msg != null ? msg : var8.toString(), element);
            return null;
        }
    }

    return definition;
}

这里就是四个步骤:

  1. 先获取id,String id = this.resolveId(element, definition, parserContext)
  2. 再获取name并处理别名,String name = element.getAttribute(“name”)
  3. 封装bean对应class 对象BeanDefinitionHolder
  4. 完成注册 this.registerBeanDefinition(holder, parserContext.getRegistry());

这里其实和之前解析默认标签最后完成注册的流程相同。

  • 解析过程总结:

总体流程其实是通过<stu:user/> 中的 beans标签中的配置 xmlns:stu=“http://www.fans.com/schema/user” ,找到处理器,然后通过user标签对应上面 init 方法对应的转换类StudentBeanDefinationParser 解析对象,找到后就进了真正自定义解析的过程,最后完成注册。

3. 总结

对比默认标签bean的解析,这里也是比较简单的,也有可能是万事开头难,完成bean标签的解析,之后就感觉 纵享丝滑

再接再厉,加油!共勉!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值