Spring源码解密之自定义标签与解析
作者:唐亚峰 发布时间:2023-11-25 01:11:34
前言
在 上一节 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
来源:http://blog.battcn.com/2018/01/12/spring/spring-3/
猜你喜欢
- Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常
- 记得老师讲课的时候,经常会用PPT遥控翻页笔来遥控幻灯片来给我们讲课,当时觉得非常有趣,由于这段时间接触了VSTO相关的开发,了解到了Off
- Java实现简单的类似QQ聊天工具,供大家参考,具体内容如下所使用到的知识点:java socket编程之TCP协议java Swing简单
- Android仿通话来电界面,供大家参考,具体内容如下简介:开发中需要模拟来电时的通话界面,仿照来电界面实现来电时播放铃声,界面通过动画模拟
- 本文介绍MediaPlayer的使用。MediaPlayer可以播放音频和视频,另外也可以通过VideoView来播放视频,虽然VideoV
- 简介由于最近的项目需求,需要在把配置类导入到容器中,通过查询,使用@Import注解就能实现这个功能,@Import注解能够帮我们吧普通配置
- 前言数据库访问是web应用必不可少的部分。现今最常用的数据库ORM框架有Hibernate与Mybatis,Hibernate貌似在传统IT
- 本文实例讲述了基于C#实现XML文件读取工具类。分享给大家供大家参考。具体如下:这是我去年写的一个XML文件读取工具类,现在做了一些调整 基
- ⭐️前面的话⭐️本篇文章带大家认识Java语法——泛型与通配符,泛型和通配符是一个非常抽象的概念,简
- 前言.NET 生态越来越好,初学的朋友也越来越多。处理同一件简单的问题,随着我们知识的积累解决问题的方法也会越来越多。开始学习一门新的语言,
- 我就废话不多说了,大家还是直接看代码吧~<!-- 查询物品的id --><select id="checkIte
- 在安卓开发中,会碰到选开始日期和结束日期的问题。特别是在使用Pad时,如果弹出一个Dialog,能够同时选择开始日期和结束日期,那将是极好的
- 本文实例为大家分享了UnityShader百叶窗展示的具体代码,供大家参考,具体内容如下shader实现以上百叶窗效果,主要通过shader
- 这篇文章主要介绍了Java如何实现支付宝电脑支付基于servlet版本,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学
- 基于 springboot+vue的测试平台开发一、前端环境搭建在前端框架vue-element-admin这个项目中,有一个简洁轻量型的项
- 一、# List泛型集合集合是OOP中的一个重要概念,C#中对集合的全面支持更是该语言的精华之一。为什么要用泛型集合?在C# 2.0之前,主
- 本文实例为大家分享了java基于UDP实现在线聊天的具体代码,供大家参考,具体内容如下效果图:一、学习UDP的简单使用步骤接收端:Datag
- 题外由于idea原因 用注解test无法在控制台上输入所以写死到程序里了,版本都30.9102了为什么还是这样啊,intelJ你们该反思了!
- MyBatis Generator简介MyBatis Generator(MBG)是MyBatis MyBatis 和iBATIS的代码生成
- 方式一:四舍五入double f = 111231.5585;四舍五