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

 更新时间:2018年01月15日 10:52:48   作者:唐亚峰  
这篇文章主要给大家介绍了关于Spring源码解密之自定义标签与解析的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参借鉴,下面随着小编来一起学习学习吧。

前言

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

自定义标签

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

定义 XSD 文件

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

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.battcn.com/schema/battcn" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.battcn.com/schema/battcn"
 elementFormDefault="qualified"
 attributeFormDefault="unqualified">
 <xsd:import namespace="http://www.springframework.org/schema/beans" />
 <xsd:element name="application">
 <xsd:complexType>
 <xsd:complexContent>
 <xsd:extension base="beans:identifiedType">
  <xsd:attribute name="name" type="xsd:string" use="required"/>
 </xsd:extension>
 </xsd:complexContent>
 </xsd:complexType>
 </xsd:element>
</xsd:schema>
  • 声明命名空间: 值得注意的是 xmlns 与 targetNamespace 可以是不存在,只要映射到指定 XSD 就行了。
  • 定义复合元素: 这里的 application 就是元素的名称,使用时 <battcn:application id="battcn"/>
  • 定义元素属性: 元素属性就是 attribute 标签,我们声明了一个必填的 name 的属性,使用时 <battcn:application id="battcn" name="Levin"/>

定义解析规则

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 容器,标签 <battcn:application /> 应该由那个解析器解析(这里是我们自定义的: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 文件,定义如下

<?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: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">
 <battcn:application id="battcn" name="Levin"/>
</beans>

创建一个测试类,如果看到控制台输出了 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.如图所示

源码分析

自定义标签解析入口

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<String, Object> 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) {
 // 解析出 <battcn:application /> 中的 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 方法进行进一步解析。最后就跟解析默认标签的套路一样了…

总结

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

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

说点什么

全文代码:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter2

相关文章

  • SpringBoot+Redis+Lua分布式限流的实现

    SpringBoot+Redis+Lua分布式限流的实现

    本文主要介绍了SpringBoot+Redis+Lua分布式限流的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • java集合类源码分析之Set详解

    java集合类源码分析之Set详解

    下面小编就为大家带来一篇java集合类源码分析之Set详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • Java I/O流使用示例详解

    Java I/O流使用示例详解

    Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。本文将通过示例为大家详细讲讲 I/O流的使用教程,需要的可以参考一下
    2022-08-08
  • Java实现的自定义类加载器示例

    Java实现的自定义类加载器示例

    这篇文章主要介绍了Java实现的自定义类加载器,结合具体实例形式分析了java自定义类加载器的原理与具体实现技巧,需要的朋友可以参考下
    2019-07-07
  • Java8中StringJoiner类的使用详解

    Java8中StringJoiner类的使用详解

    Java在java.util包中添加了一个新的最终类StringJoiner。可以用于构造由定界符分隔的字符序列。本文将通过示例和大家分享一下StringJoiner类的使用,需要的可以参考一下
    2022-10-10
  • SpringMVC Cron定时器Demo常见问题解决方案

    SpringMVC Cron定时器Demo常见问题解决方案

    这篇文章主要介绍了SpringMVC Cron定时器Demo常见问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Javaweb实现在线人数统计代码实例

    Javaweb实现在线人数统计代码实例

    这篇文章主要介绍了Javaweb实现在线人数统计代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Spring MVC异常处理机制示例详解

    Spring MVC异常处理机制示例详解

    这篇文章主要给大家介绍了关于Spring MVC异常处理机制的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring MVC具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-11-11
  • Spring Boot 功能整合的实现

    Spring Boot 功能整合的实现

    Spring Boot生态丰富,集成也不算困难。本文简单的介绍下功能整合的步骤,最后提供一个具体的实现例子,学习Spring Boot的同学可以参考下
    2021-05-05
  • Java 如何在switch case语句中声明变量

    Java 如何在switch case语句中声明变量

    这篇文章主要介绍了Java 如何在switch case语句中声明变量,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10

最新评论