《Spring源码深度分析》第4章 自定义标签的解析
目录标题
- 前言
- 一、自定义标签使用
- 二、自定义标签解析
- 1、代码入口
- 2、parseCustomElement【BeanDefinitionParserDelegate】
- 2.1 resolve【DefaultNamespaceHandlerResolver】
- 3、parse【NamespaceHandlerSupport】
- 4、parse【AbstractBeanDefinitionParser】
- 4.1 parseInternal【AbstractSingleBeanDefinitionParser】
- 总结
前言
汇总:《Spring源码深度分析》持续更新中…
在之前的章节中,我们提到了在 Spring 中存在默认标签与自定义标签两种,而在上一章节中我们分析了 Spring 中对默认标签的解析过程,相信大家一定已经有所感悟。那么,现在将开始新的里程,分析 Spring 中自定义标签的加载过程。
一、自定义标签使用
在分析Spring的自定义标签源码之前,我们可以自己手写一个自定义标签:
【Spring学习】Spring自定义标签详细步骤
二、自定义标签解析
1、代码入口
DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {// 解析默认的标签元素parseDefaultElement(ele, delegate);}else {// 解析自定义的标签元素delegate.parseCustomElement(ele);}}}}// 解析自定义的标签元素else {delegate.parseCustomElement(root);}}
2、parseCustomElement【BeanDefinitionParserDelegate】
@Nullablepublic BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取自定义标签的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间,获取对应的'自定义标签处理器'(spring.handlers)NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 进行解析(NamespaceHandlerSupport的parse方法)return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}
2.1 resolve【DefaultNamespaceHandlerResolver】
此处会调用子类的init方法,用于【注册解析器】。
public NamespaceHandler resolve(String namespaceUri) {// 格式:Map<NameSpace,HandlerClassPathName>Map<String, Object> handlerMappings = getHandlerMappings();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);// 调用MyNamespaceHandler的init方法。注册解析器:registerBeanDefinitionParser("user", new UserBeanDefinitionParser());namespaceHandler.init();handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;}catch (ClassNotFoundException ex) {throw new FatalBeanException("Could not find NamespaceHandler class [" + className +"] for namespace [" + namespaceUri + "]", ex);}catch (LinkageError err) {throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +className + "] for namespace [" + namespaceUri + "]", err);}}}
3、parse【NamespaceHandlerSupport】
@Override@Nullablepublic BeanDefinition parse(Element element, ParserContext parserContext) {// 该自定义标签的解析器BeanDefinitionParser parser = findParserForElement(element, parserContext);// 调用AbstractBeanDefinitionParser中的parse方法return (parser != null ? parser.parse(element, parserContext) : null);}
4、parse【AbstractBeanDefinitionParser】
public final BeanDefinition parse(Element element, ParserContext parserContext) {// (重点)parseInternal是真正做解析的方法。 AbstractSingleBeanDefinitionParser//在 parseInternal 中并不是直接调用自定义的 doParse 函数,而是进行了一系列的数据准备,包括对 beanClass、 scope、lazyInit 等属性的准备。AbstractBeanDefinition definition = parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) {try {String id = resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element)+ "' when used as a top-level tag", element);}String[] aliases = null;if (shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);// 注册definition 和 aliasregisterBeanDefinition(holder, parserContext.getRegistry());if (shouldFireEvents()) {BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);postProcessComponentDefinition(componentDefinition);//parserContext.registerComponent(componentDefinition);}}catch (BeanDefinitionStoreException ex) {String msg = ex.getMessage();parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);return null;}}return definition;}
4.1 parseInternal【AbstractSingleBeanDefinitionParser】
此处会调用doParse方法,用于【解析自定义标签】。
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);}// (关键)调用子类的doParse方法。 自定义解析器的doParse方法doParse(element, parserContext, builder);return builder.getBeanDefinition();}
总结
回顾一下全部的自定义标签处理过程,虽然在实例中我们定义 UserBeanDefinitionParser,但是在其中我们只是做了与自己业务逻辑相关的部分。不过我们没做但是并不代表没有,在这个处理过程中同样也是按照 Spring 中默认标签的处理方式进行,包括创建 BeanDefinition 以及进行相应默认属性的设置,对于这些工作 Spring 都默默地帮我们实现了,只是**暴露出一些接口来供用户实现个性化的业务**。
通过对本章的了解,相信读者对 Spring 中自定义标签的使用以及在解析自定义标签过程中 Spring 为我们做了哪些工作会有一个全面的了解。到此为止我们已经完成了 Spring 中全部的解析工作,也就是说到现在为止我们已经理解了 Spring 将 bean 从配置文件到加载到内存中的全过程,而接下来的任务便是如何使用这些 bean,下一章将介绍 bean的加载。
相关文章:
《Spring源码深度分析》第4章 自定义标签的解析
目录标题前言一、自定义标签使用二、自定义标签解析1、代码入口2、parseCustomElement【BeanDefinitionParserDelegate】2.1 resolve【DefaultNamespaceHandlerResolver】3、parse【NamespaceHandlerSupport】4、parse【AbstractBeanDefinitionParser】4.1 parseInternal【Abst…...

MATLAB绘制椭圆形相关系矩阵图
数据/代码准备 数据及代码下载: 下载专区-《MATLAB统计分析与应用:40个案例分析》程序与数据 绘图函数: matrixplot(data, PARAM1,val1, PARAM2,val2, ...) 案例 数据如下: MATLAB代码如下: clc close all clear …...
「SQL面试题库」 No_1 员工薪水中位数
🍅 1、专栏介绍 「SQL面试题库」是由 不是西红柿 发起,全员免费参与的SQL学习活动。我每天发布1道SQL面试真题,从简单到困难,涵盖所有SQL知识点,我敢保证只要做完这100道题,不仅能轻松搞定面试ÿ…...

Python机器学习17——极限学习机(ELM)
本系列基本不讲数学原理,只从代码角度去让读者们利用最简洁的Python代码实现机器学习方法。 背景: 极限学习机(ELM)也是学术界常用的一种机器学习算法,严格来说它应该属于神经网络,应该属于深度学习栏目,但是我这里把它…...

二分查找与判定树
二分查找的算法思想二分查找也称“折半查找”,要求查找表为采用顺序存储结构的有序表。本例一律采用升序排列。二分查找每一次都会比较给定值与序列[low,high]的中间元素,该元素的下标为mid (lowhigh)/2,若两者相等,则返回元素的下标为mid;如…...

反转链表(精美图示详解哦)
全文目录引言反转链表题目描述与思路实现总结引言 在学习了单链表的相关知识后,尝试实现一些题目可以帮助我们更好的理解单链表的结构以及对其的使用。 从这篇文章开始,将会介绍一些编程题来帮助我们更好的掌握单链表: 分别是反转链表、链表…...

深入理解多线程
一、线程基本概念 1、概述 线程是允许应用程序并发的一种机制。线程共享进程内的所有资源。 线程是调度的基本单位。 每个线程都有自己的 errno。 所有 pthread 函数均以返回 0 表示成功,返回一个正值表示失败。 编译 pthread 程序需要添加链接库(…...
华为OD机试题 - 英文输入法(JavaScript)
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解: 英文输入法题目输入输出示例一输入输出说明示例一输入输出Code…...
64 云原生容器化
文章目录 一、什么是rancher二、为什么使用rancher三、 Rancher与[k8s](https://so.csdn.net/so/search?q=k8s&spm=1001.2101.3001.7020)的关系及区别1、Rancher具有的优势三、rancher安装1、细部介绍四、图形化操作1、执行2、图形化操作1、进行客户机登录rancher2、Ranch…...

IronXL for .NET 2023.2.5 Crack
关于适用于 .NET 的 IronXL 在 C# 中阅读和编辑 Excel 电子表格,无需 MS Office 或 Excel Interop。 IronXL for .NET 允许开发人员在 .NET 应用程序和网站中读取、生成和编辑 Excel(和其他电子表格文件)。您可以读取和编辑 XLS/XLSX/CSV/TS…...

计算机组成原理|第一章(笔记)
目录第一章 计算机系统概论1.1 计算机系统简介1.1.1 计算机的软硬件概念1.1.2 计算机系统的层次结构1.1.3 计算机组成和计算机体系结构1.2 计算机的基本组成1.2.1 冯 诺伊曼计算机的特点1.2.2 计算机的硬件框图1.2.3 计算机的工作过程1.3 计算机硬件的主要技术指标1.3.1 机器字…...

[ vulnhub靶机通关篇 ] Empire Breakout 通关详解
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...

IP定位离线库有什么作用?
IP离线是什么意思?我们以丢失手机为例来寻找它,现在手机都有IP定位功能,只要手机开通了IP定位,就能找到手机。iPhone定位显示离线一般是iPhone手机关机了或者iPhone手机中“查找我的iPhone”功能关闭了。如果手机在手中的话可以打…...

[C++]vector模拟实现
目录 前言: 1. vector结构 2. 默认成员函数 2.1 构造函数 无参构造: 有参构造: 有参构造重载: 2.2 赋值运算符重载、拷贝构造(难点) 2.3 析构函数: 3. 扩容 3.1 reserve 3.2 resize…...

DevOps实战50讲-(2)Jenkins配置
1. Docker镜像方式安装拉取Jenkins镜像docker pull jenkins/jenkins编写docker-compose.ymlversion: "3.1" services:jenkins:image: jenkins/jenkinscontainer_name: jenkinsports:- 8080:8080- 50000:50000volumes:- ./data/:/var/jenkins_home/首次启动会因为数据…...

LC-1599. 经营摩天轮的最大利润(贪心)
1599. 经营摩天轮的最大利润 难度中等39 你正在经营一座摩天轮,该摩天轮共有 4 个座舱 ,每个座舱 最多可以容纳 4 位游客 。你可以 逆时针 轮转座舱,但每次轮转都需要支付一定的运行成本 runningCost 。摩天轮每次轮转都恰好转动 1 / 4 周。…...

Umi使用百度地图服务
需求描述 需要在前端页面中使用地图定位功能,所以在前端umi项目中使用百度地图服务,由于umi项目默认没有入口的html文件,所以无法通过常规的在head中加入外链js的方式使用 百度ak zyqeLCzvQPCCNImRu9yRGOqWlEUicxxGreact使用百度api 链接:…...

js中getBoundingClientRect()方法
getBoundingClientRect()返回值是一个 DOMRect 对象,是包含整个元素的最小矩形(包括 padding 和 border-width)。该对象使用 left、top、right、bottom、x、y、width 和 height 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 …...
Unity记录2.2-动作-动画、相机、Debug与总结
文章首发及后续更新:https://mwhls.top/4453.html,无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评,非常感谢! 汇总:Unity 记录 摘要:重写了动画触…...

分享十个前端Web3D可视化框架附地址
Three.js:Three.js是一个流行的3D库,提供了大量的3D功能,包括基本几何形状、材质、灯光、动画、特效等。它是一个功能强大、易于使用的框架,广泛用于Web3D可视化应用程序的开发。Three.js:https://threejs.org/Babylon…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...