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

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

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

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


Table of Contents generated with DocToc

  • 官方例子
  • 自定义标签使用定义普通的 POJO 组件定义 XSD 描述文件定义组件解析器创建处理类的注册器编写 spring.hanlders 和 spring.schemas 文件使用 Demo配置文件测试代码小结
  • 自定义标签解析① 获取标签的命名空间② 根据命名空间找到对应的 NamespaceHandler③ 调用自定义的 NamespaceHandler 进行解析
  • 总结
  • 参考资料

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

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

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

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


官方例子

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

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

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

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

中间的是缓存定义的 xsd 文件,通过 定义元素, 区间内定义属性列表, 定义单个属性,详细分析可以看下注释~

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

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

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


自定义标签使用

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

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

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


定义普通的 POJO 组件

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

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

定义 XSD 描述文件

custom-product.xsd

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


定义组件解析器

base.label.custom.ProductBeanDefinitionParser

public class ProductBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {@Overrideprotected Class getBeanClass(Element element) {// 返回对应的类型return Product.class;}// 从 element 中解析并提取对应的元素@Overrideprotected 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 {@Overridepublic void init() {// 将组件解析器进行注册到 `Spring` 容器registerBeanDefinitionParser("product", new ProductBeanDefinitionParser());}}

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


编写 spring.hanlders 和 spring.schemas 文件

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

spring.handlers

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

spring.schemas

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

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


使用 Demo

配置文件

<?xml version="1.0" encoding="UTF-8"?>

测试代码

public class ProductBootstrap {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("custom/custom-label.xml");Product product = (Product) context.getBean("product");// 输出 Product{, productId ='1', unit='台', name='Apple'}System.out.println(product.toString());}}

小结

现在来回顾一下,Spring 遇到自定义标签是,加载自定义的大致流程:

  • 定位 spring.hanlders 和 spring.schemas:在两个文件中找到对应的 handler 和 XSD,默认位置在 resources -> META-INF。
  • Handler 注册 Parser:扩展了 NamespaceHandlerSupport 的类,在初始化注册解析器
  • 运行解析器 Parser:扩展了 AbstractSingleBeanDefinitionParser,通过重载方法进行属性解析,完成解析。

上面已经将自定义注解的使用讲了,接下来讲的是源码中如何对自定义标签进行解析。


自定义标签解析

在上一篇笔记中,讲了如何解析默认标签,Spring 判断一个标签不是默认标签的话,就会将这个标签解析交给自定义标签的解析方法

直接定位到解析自定义标签的方法吧:

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 注释 3.8 ① 找到命名空间String namespaceUri = getNamespaceURI(ele);// ② 根据命名空间找到对应的 NamespaceHandlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);// ③ 调用自定义的 NamespaceHandler 进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}

看着流程是不是觉得很熟悉,我们刚才在自定义标签使用时,定义的文件顺序是一样的,下面来讲下这三个方法,具体代码不会贴太多,主要记录一些关键方法和流程,详细代码和流程请下载我上传的工程~


① 获取标签的命名空间

public String getNamespaceURI(Node node) {return node.getNamespaceURI();}

这个方法具体做的事情很简单,而且传参的类型 org.w3c.dom.Node,已经提供了现成的方法,所以我们只需要调用即可。


② 根据命名空间找到对应的 NamespaceHandler

具体解析方法这这个类中:

org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve

public NamespaceHandler resolve(String namespaceUri) {// 注释 3.9 获取所有已经配置的 handler 映射Map handlerMappings = getHandlerMappings();// 从 map 中取出命名空间对应的 NamespaceHandler 的 className// 这个映射 map 值,没有的话,会进行实例化类,然后放入 map,等下次同样命名空间进来就能直接使用了Object handlerOrClassName = handlerMappings.get(namespaceUri);if (handlerOrClassName == null) {return null;}else if (handlerOrClassName instanceof NamespaceHandler) {return (NamespaceHandler) handlerOrClassName;}else {String className = (String) handlerOrClassName;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 的 init() 方法namespaceHandler.init();// 放入 handler 映射中handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;}}

找对应的 NamespaceHandler,关键方法在于 getHandlerMappings():

private Map getHandlerMappings() {Map handlerMappings = this.handlerMappings;// 如果没有缓存,进行缓存加载,公共变量,加锁进行操作,细节好评if (handlerMappings == null) {synchronized (this) {handlerMappings = this.handlerMappings;if (handlerMappings == null) {Properties mappings =PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);handlerMappings = new ConcurrentHashMap<>(mappings.size());CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);this.handlerMappings = handlerMappings;}}}return handlerMappings;}

所以我们能看到,找 Handler 时,使用的策略是延迟加载,在 map 缓存中找到了直接返回,没找到对应的 Handler,将处理器实例化,执行 init() 方法,接着将 Handler 放入 map 缓存中,等待下一个使用。


③ 调用自定义的 NamespaceHandler 进行解析

回忆一下,我们在自定义标签解析的时候,是没有重载 parse() 方法,所以定位进去,看到实际调用方法是这两行:

org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse

public BeanDefinition parse(Element element, ParserContext parserContext) {// 寻找解析器并进行解析操作BeanDefinitionParser parser = findParserForElement(element, parserContext);// 真正解析调用调用的方法return (parser != null ? parser.parse(element, parserContext) : null);}

第一步获取解析器,就是我们之前在 init() 方法中,注册到 Spring 容器的解析器。

第二步才是解析器进行解析的方法,我们的解析器扩展的是 AbstractSingleBeanDefinitionParser,所以实际是调用了我们解析器父类的父类 AbstractBeanDefinitionParser 的 parse 方法:

org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse

public final BeanDefinition parse(Element element, ParserContext parserContext) {// 注释 3.10 实际自定义标签解析器调用的方法,在 parseInternal 方法中,调用了我们重载的方法AbstractBeanDefinition definition = parseInternal(element, parserContext);    ...    return definition;}

解析关键方法

org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();String parentName = getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}Class> beanClass = getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);}else {String beanClassName = getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));BeanDefinition containingBd = parserContext.getContainingBeanDefinition();if (containingBd != null) {// Inner bean definition must receive same scope as containing bean.builder.setScope(containingBd.getScope());}if (parserContext.isDefaultLazyInit()) {// Default-lazy-init applies to custom bean definitions as well.builder.setLazyInit(true);}// 注释 3.11 在这里调用了我们写的解析方法doParse(element, parserContext, builder);return builder.getBeanDefinition();}

这里我要倒着讲,在第二步解析时,不是直接调用了自定义的 doParse 方法,而是进行了一系列的数据准备,包括了 beanClass、 class、 lazyInit 等属性的准备。

第一步解析,在我省略的代码中,是将第二步解析后的结果进行包装,从 AbstractBeanDefinition 转换成 BeanDefinitionHolder ,然后进行注册。转换和注册流程在第一篇笔记已经介绍过了,不再赘述。

到这里为止,我们自定义标签的解析就完成了~

修改meta标签 查看源码没效果怎么办_Spring 源码学习(三)-自定义标签相关推荐

  1. javaweb学习6——自定义标签

    声明:本文只是自学过程中,记录自己不会的知识点的摘要,如果想详细学习JavaWeb,请到孤傲苍狼博客学习,JavaWeb学习点此跳转 本文链接:https://www.cnblogs.com/xdp- ...

  2. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

  3. component是什么接口_逐行解读Spring(二)什么,自定义标签没听说过?

    一.自定义标签是什么? 上一篇我们讲了默认标签-bean标签的解析,今天我们讲一下自定义标签的解析. 1. 自定义标签的定义 这个问题其实上一篇有讲过,这边再复述一遍,在spring的xml配置文件中 ...

  4. JSP自定义标签就是如此简单

    2019独角兽企业重金招聘Python工程师标准>>> 为什么要用到简单标签? 上一篇博客中我已经讲解了传统标签,想要开发自定义标签,大多数情况下都要重写doStartTag(),d ...

  5. 自定义标签JSTL标签库详解

     自定义标签是JavaWeb的一部分非常重要的核心功能,我们之前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片段,下面就来看一下自定义标签的简 ...

  6. JSP自定义标签学习笔记

    http://blog.csdn.net/liangbinny/article/details/6309893 本文是本人学习<JavaWeb学习>书籍所记下的学习笔记 一.概述 JSP ...

  7. Jsp2.0自定义标签(第二天)——自定义循环标签

    今天是学习自定义标签的第二天,主要是写一个自定义的循环标签. 先看效果图: 前台页面Jsp代码 <%@ page language="java" contentType=&q ...

  8. java 自定义循环标签_Jsp2.0自定义标签(第二天)——自定义循环标签

    今天是学习自定义标签的第二天,主要是写一个自定义的循环标签. 先看效果图: 前台页面Jsp代码 String[]array = new String[]{"one","t ...

  9. jstl自定义标签接口介绍

    2019独角兽企业重金招聘Python工程师标准>>> 1. JspTag接口 JspTag接口是所有自定义标签的父接口,它是JSP2.0中新定义的一个标记接口,没有任何属性和方法. ...

最新文章

  1. Jquery 将后台返回的字符串转换成Json格式的数据
  2. linux中的NFS服务器配置及/etc/exports
  3. 【阿里云课程】如何从零开始完成一个完整的CV项目
  4. lstm 变长序列_keras在构建LSTM模型时对变长序列的处理操作
  5. 网站搭建从零开始(三)域名解析
  6. 揭开不一样的世界,这5部纪录片绝对不能错过!
  7. Redis Client On Error: Error: connect ECONNREFUSED 192.168.xxx.105:6379 Config right?
  8. d.php xfso_centos平台基于snort、barnyard2以及base的IDS(入侵检测系统)的搭建与测试及所遇问题汇总...
  9. 使用 Nginx + Gunicorn 部署 Flask 项目
  10. 华为鸿蒙平板界面,华为工作人员曝光:鸿蒙OS正式版,平板手机将采用全新UI界面...
  11. 华为荣耀手机指令代码大全_2020.10月《各价位华为、荣耀手机推荐》
  12. Windows7安装VC2015-2019_redist.x64提示“设置失败0xc8000222-未指定的错误”
  13. 浏览器显示json格式
  14. 订单功能模块设计与实现
  15. 一次真实的应急响应案例(Centos)——暴力破解、替换ps命令、留多个后门——事件复现(含靶场环境)
  16. 【表格样式】——HTML制作表格大全
  17. JavaWeb-JSP的IDEA创建项目、配置tomcat、导入jar包及JavaBean连接数据库
  18. Python【爬虫实战】爬取美女壁纸资源
  19. ui和平面设计的区别?ui设计做什么的?
  20. 怎样grep出匹配组的信息并得到唯一输出

热门文章

  1. 正则化方法/防止过拟合提高泛化能力的方法:L1和L2 regularization、数据集扩增、dropout
  2. Cookie、Session、Token那点事儿
  3. mybatis的缓存机制(一级缓存二级缓存和刷新缓存)和mybatis整合ehcache
  4. 并发编程(四):也谈谈数据库的锁机制
  5. Java并发:volatile内存可见性和指令重排
  6. 词袋模型BoW图像检索Python实战
  7. 用 python 实现一个多线程网页下载器
  8. 多种特征提取算法比较汇总
  9. 聚类 K-Means Using Python
  10. 深度译文:机器学习那些事