java 自定义xml标签解析,Spring源码解密之自定义标签与解析

前言

在 上一节 Spring解密 - 默认标签的解析 中,重点分析了 Spring 对默认标签是如何解析的,那么本章继续讲解标签解析,着重讲述如何对自定义标签进行解析。话不多说了,来一起看看详细的介绍吧。

自定义标签

在讲解 自定义标签解析 之前,先看下如何自定义标签

定义 XSD 文件

定义一个 XSD 文件描述组件内容

elementFormDefault="qualified"

attributeFormDefault="unqualified">

声明命名空间: 值得注意的是 xmlns 与 targetNamespace 可以是不存在,只要映射到指定 XSD 就行了。

定义复合元素: 这里的 application 就是元素的名称,使用时

定义元素属性: 元素属性就是 attribute 标签,我们声明了一个必填的 name 的属性,使用时

定义解析规则

1.创建一个类实现 BeanDefinitionParser 接口(也可继承 Spring 提供的类),用来解析 XSD 文件中的定义和组件定义

public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override

protected Class getBeanClass(Element element) {

// 接收对象的类型 如:String name = (String) context.getBean("battcn");

return String.class;

}

@Override

protected void doParse(Element element, BeanDefinitionBuilder bean) {

// 在 xsd 中定义的 name 属性

String name = element.getAttribute("name");

bean.addConstructorArgValue(name);

}

}

这里创建了一个 ApplicationBeanDefinitionParser 继承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser 的子类), 重点就是重写的 doParse,在这个里面解析 XML 标签的,然后将解析出的 value(Levin) 通过构造器方式注入进去

2.创建一个类继承 NamespaceHandlerSupport 抽象类

public class BattcnNamespaceHandler extends NamespaceHandlerSupport {

@Override

public void init() {

registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser());

}

}

BattcnNamespaceHandler 的作用特别简单,就是告诉 Spring 容器,标签 应该由那个解析器解析(这里是我们自定义的:ApplicationBeanDefinitionParser),负责将组件注册到 Spring 容器

3.编写 spring.handlers 和 spring.schemas 文件

文件存放的目录位于 resources/META-INF/文件名

spring.handlers

http\://www.battcn.com/schema/battcn=com.battcn.handler.BattcnNamespaceHandler

spring.schemas

http\://www.battcn.com/schema/battcn.xsd=battcn.xsd

4.使用自定义标签

申明 bean.xml 文件,定义如下

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:battcn="http://www.battcn.com/schema/battcn" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.battcn.com/schema/battcn

http://www.battcn.com/schema/battcn.xsd">

创建一个测试类,如果看到控制台输出了 Levin 字眼,说明自定义标签一切正常

public class Application {

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

String name = (String) context.getBean("battcn");

System.out.println(name);

}

}

5.如图所示

8e629e2c3dd83e51416ebb36f14b36cf.png

源码分析

自定义标签解析入口

public class BeanDefinitionParserDelegate {

@Nullable

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {

// 获取命名空间地址 http://www.battcn.com/schema/battcn

String namespaceUri = getNamespaceURI(ele);

if (namespaceUri == null) {

return null;

}

// NamespaceHandler 就是 自定义的 BattcnNamespaceHandler 中注册的 application

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));

}

}

与默认标签解析规则一样的是,都是通过 getNamespaceURI(Node node)来获取命名空间,那么 this.readerContext.getNamespaceHandlerResolver()是从哪里获取的呢?我们跟踪下代码,可以发现在项目启动的时候,会在 XmlBeanDefinitionReader 将所有的 META-INF/spring.handles 文件内容解析,存储在 handlerMappers(一个ConcurrentHashMap) 中,在调用 resolve(namespaceUri)校验的时候在将缓存的内容提取出来做对比

public class XmlBeanDefinitionReader {

public NamespaceHandlerResolver getNamespaceHandlerResolver() {

if (this.namespaceHandlerResolver == null) {

this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();

}

return this.namespaceHandlerResolver;

}

}

resolve

1.加载指定的 NamespaceHandler 映射,并且提取的 NamespaceHandler 缓存起来,然后返回

public class DefaultNamespaceHandlerResolver {

@Override

@Nullable

public NamespaceHandler resolve(String namespaceUri) {

Map handlerMappings = getHandlerMappings();

// 从 handlerMappings 提取 handlerOrClassName

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) BeanUtils.instantiateClass(handlerClass);

// Handler 初始化

namespaceHandler.init();

handlerMappings.put(namespaceUri, namespaceHandler);

return namespaceHandler;

}

catch (ClassNotFoundException ex) {

throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +

namespaceUri + "] not found", ex);

}

catch (LinkageError err) {

throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +

namespaceUri + "]: problem with handler class file or dependent class", err);

}

}

}

}

标签解析

加载完 NamespaceHandler 之后,BattcnNamespaceHandler 就已经被初始化为 了,而 BattcnNamespaceHandler 也调用了init()方法完成了初始化的工作。因此就接着执行这句代码: handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));具体标签解。

public class NamespaceHandlerSupport {

@Override

@Nullable

public BeanDefinition parse(Element element, ParserContext parserContext) {

BeanDefinitionParser parser = findParserForElement(element, parserContext);

return (parser != null ? parser.parse(element, parserContext) : null);

}

@Nullable

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {

// 解析出 中的 application

String localName = parserContext.getDelegate().getLocalName(element);

BeanDefinitionParser parser = this.parsers.get(localName);

if (parser == null) {

parserContext.getReaderContext().fatal(

"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);

}

return parser;

}

}

简单来说就是从 parsers 中寻找到 ApplicationBeanDefinitionParser 实例,并调用其自身的 doParse 方法进行进一步解析。最后就跟解析默认标签的套路一样了…

总结

熬过几个无人知晓的秋冬春夏,撑过去一切都会顺着你想要的方向走…

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

说点什么

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值