Spring 源码学习(三)-自定义标签(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 又来填坑啦,上一篇讲完默认标签的解析,这篇笔记记录一下自定义标签的解析吧。我们知道,Spring 源码的核心模块是 Spring-core 和 Spring-beans,在此基础上衍生出其他模块,例如 context、 cache、 tx 等模块,都是根据这两个基础模块进行扩展的。聪明如你,应该想到我们代码中常用的缓存注解 @Cacheable、事务注解 @Transaction,还有阿里巴巴的 RPC 中间件 Dubbo,在配置文件中通过 <dubbo:service/> 或者 <dubbo:reference/> 进行服务注册和订阅,这些都都属于 Spring 的自定义标签的实现,

Table of Contentsgenerated with DocToc[1]

官方例子[2]自定义标签使用[3]

定义普通的 POJO 组件[4]定义 XSD 描述文件[5]定义组件解析器[6]创建处理类的注册器[7]编写 spring.hanldersspring.schemas 文件[8]使用 Demo[9]

配置文件[10]测试代码[11]

小结[12]


自定义标签解析[13]

① 获取标签的命名空间[14]② 根据命名空间找到对应的 NamespaceHandler[15]③ 调用自定义的 NamespaceHandler 进行解析[16]

总结[17]参考资料[18]




又来填坑啦,上一篇讲完默认标签的解析,这篇笔记记录一下自定义标签的解析吧。

我们知道,Spring 源码的核心模块是 Spring-coreSpring-beans,在此基础上衍生出其他模块,例如 contextcachetx 等模块,都是根据这两个基础模块进行扩展的。

聪明如你,应该想到我们代码中常用的缓存注解 @Cacheable、事务注解 @Transaction,还有阿里巴巴的 RPC 中间件 Dubbo,在配置文件中通过 <dubbo:service/> 或者 <dubbo:reference/> 进行服务注册和订阅,这些都都属于 Spring 的自定义标签的实现,通过自定义标签可以实现更加强大的功能!

作为一个有追求的程序员,当然不能满足于框架自带默认的标签,为了扩展性和配置化要求,这时候就需要学习自定义标签和使用自定义标签~


官方例子

先来看一张源码图片(红框框圈着是重点哟)

20.jpg

刚才说了缓存和事务,那就拿这两个举例,还有一个标签 <myname:>(这个我也不太清楚,网上查的资料也不多,所以按照我的理解大家跟说下)

首先我们看到,<tx><cache><mvc><myname> 都是自定义标签,左一是配置文件,进行 bean 的定义,顶部的 xmlns 是命名空间,表示标签所属的定义文件,像事务、缓存、MVC 的命名空间都是固定的。

myname 相当于万金油,既可以定义为事务,又可以定义为缓存,只要我们在命名空间中进行相应的定义就能正确的识别。这个就是我们待会要使用到的自定义标签,通过命名空间定位到我们想要的处理逻辑。

中间的是缓存定义的 xsd 文件,通过 <xsd:element name="annotation-driven"> 定义元素,<xsd:complexType> 区间内定义属性列表,<xsd:attribute> 定义单个属性,详细分析可以看下注释~

右边的是事务定义的 xsd 文件,大体内容的跟中间一样,虽然元素名称 <annotation-driven> 有相同的,但是下面的属性定义是有所区别的。

所以我们对自定义注解有个大概的了解,xsd 描述文件是个其中一个关键,在配置文件顶部的命名空间是标签进行解析时,进行定位的配置,当然还有处理器,下面使用时进行介绍。

不知道理解的对不对,如果有误的话请大佬们指出,我会进行修改的!


自定义标签使用

Spring 提供了可扩展的 Schema 的支持,扩展 Spring 自定义标签配置需要以下几个步骤:

创建一个需要扩展的组件定义一个 XSD 描述文件创建一个文件,实现 BeanDefinitionParse 接口,用来解析 XSD 文件中的定义和组件定义。创建一个 Handler 文件,扩展自 NamespaceHandlerSupport,将组件注册到 Spring 容器编写 Spring.handlersSpring.schemas 文件

刚开始看到这些流程时,我还是有点慌的,毕竟从一个使用默认标签的萌新小白,突然要我自己定义,感觉到很新鲜,所以请各位跟着下面的流程一起来看吧~


定义普通的 POJO 组件

这个没啥好说的,就是一个普通的类:

public class Product {
    private Integer productId;
    private String unit;
    private String name;
}

定义 XSD 描述文件

custom-product.xsd

<xsd:schema targetNamespace="http://vip-augus.github.io/schema/product"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified">
    <!-- 注释 3.4 自定义元素  -->
    <xsd:element name="product">
        <xsd:complexType>
            <!-- 这个是类注册时的名字,组件中请不要占用该字段~ -->
            <xsd:attribute name="id" type="xsd:string"/>
            <!-- 属性定义列表,名字和类型 -->
            <xsd:attribute name="productId" type="xsd:integer"/>
            <xsd:attribute name="unit" type="xsd:string"/>
            <xsd:attribute name="name" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

我在上面的描述文件中,定义了一个新的 targetNamespace,同时定义了一个 叫 product 的新元素,并且将组件中的属性都列在 <xsd:attribute> 中。XSD 文件是 XMLDTD 的替代者,具体就不多深入,感兴趣的同学可以继续深入了解。


定义组件解析器

base.label.custom.ProductBeanDefinitionParser

public class ProductBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element element) {
        // 返回对应的类型
        return Product.class;
    }
    // 从 element 中解析并提取对应的元素
    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String productId = element.getAttribute("productId");
        String productName = element.getAttribute("name");
        String productUnit = element.getAttribute("unit");
        // 将提取到的数据放入 BeanDefinitionBuilder 中,等到完成所有 bean 的解析之后统一注册到 beanFactory 中
        if (productId != null) {
            // element.getAttribute("") 方法取出来的都是 string 类型,使用时记得手动转换
            builder.addPropertyValue("productId", Integer.valueOf(productId));
        }
        if (StringUtils.hasText(productName)) {
            builder.addPropertyValue("name", productName);
        }
        if (StringUtils.hasText(productUnit)) {
            builder.addPropertyValue("unit", productUnit);
        }
    }
}

关键点在于,我们的解析器是继承于 AbstractSingleBeanDefinitionParser,重载了两个方法,详细用途请看注释~


创建处理类的注册器

base.label.custom.ProductBeanHandler

public class ProductBeanHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        // 将组件解析器进行注册到 `Spring` 容器
        registerBeanDefinitionParser("product", new ProductBeanDefinitionParser());
    }
}

这个类也比较简单,关键是继承了 NamespaceHandlerSupport,对他进行了扩展,在该类初始化时将组件解析器进行注册到 Spring 容器中。


编写 spring.hanldersspring.schemas 文件

我将文件位置放在 resources -> META-INF 目录下:

spring.handlers


http\://vip-augus.github.io/schema/product=base.label.custom.ProductBeanHandler

spring.schemas


http\://vip-augus.github.io/schema/product.xsd=custom/custom-product.xsd

到了这一步,自定义的配置就结束了。下面是如何使用

相关文章
|
4天前
|
XML Java 数据格式
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
13 1
|
4天前
|
XML druid Java
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
Spring5系列学习文章分享---第二篇(IOC的bean管理factory+Bean作用域与生命周期+自动装配+基于注解管理+外部属性管理之druid)
8 0
|
3天前
|
消息中间件 安全 Java
学习认识Spring Boot Starter
在SpringBoot项目中,经常能够在pom文件中看到以spring-boot-starter-xx或xx-spring-boot-starter命名的一些依赖。例如:spring-boot-starter-web、spring-boot-starter-security、spring-boot-starter-data-jpa、mybatis-spring-boot-starter等等。
17 4
|
5天前
|
Java 数据库连接 Spring
Spring 整合 MyBatis 底层源码解析
Spring 整合 MyBatis 底层源码解析
|
7天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的中医学习服务管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的中医学习服务管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
22 5
|
6天前
|
Java Spring 容器
解读spring5源码中实例化单例bean的调用链
解读spring5源码中实例化单例bean的调用链
|
11天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的学生网课学习效果评价的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的学生网课学习效果评价的详细设计和实现(源码+lw+部署文档+讲解等)
25 7
|
11天前
|
设计模式 前端开发 Java
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
17 1
|
4天前
|
XML 安全 Java
Spring 基础知识学习
Spring 基础知识学习
|
4天前
|
Java Spring 容器
Spring5系列学习文章分享---第六篇(框架新功能系列+整合日志+ @Nullable注解 + JUnit5整合)
Spring5系列学习文章分享---第六篇(框架新功能系列+整合日志+ @Nullable注解 + JUnit5整合)
5 0