spring 对于 XML 中自定义标签的解析

我们知道,在 spring 的配置文件解析时,通过 XmlBeanDefinitionReader 来操作,下面来看看对 XML 配置文件解析时,这个类具体是如何操作的。

入口

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {Document doc = doLoadDocument(inputSource, resource);int count = registerBeanDefinitions(doc, resource);return count;
}

核心逻辑就两行:

第一行,将 xml 转换为 org.w3c.dom.Document,方便后续 registerBeanDefinitions 操作,转换使用 JAXP 中的 DOM 解析来操作。

第二行,从名字可以看出,注册所有 BeanDefinition,这也是接下来的重点。

registerBeanDefinitions

要注册 BeanDefinition,就需要先解析出 BeanDefinition。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount();documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;
}

先创建一个 BeanDefinitionDocumentReader,此处为 DefaultBeanDefinitionDocumentReader,接着通过这个类来发起注册调用,在这个之前,调用 createReaderContext 来创建 XmlReaderContext。

XmlReaderContext

public XmlReaderContext createReaderContext(Resource resource) {return new XmlReaderContext(resource, this.problemReporter, this.eventListener,this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {if (this.namespaceHandlerResolver == null) {this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();}return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());return new DefaultNamespaceHandlerResolver(cl);
}
public DefaultNamespaceHandlerResolver() {this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

逻辑很简单,由于 spring 遵循 SRP(单一职责原则),所以类会比较多。

这个 DefaultNamespaceHandlerResolver 就是完成自定义标签扩展及解析很重要的一个类。在DEFAULT_HANDLER_MAPPINGS_LOCATION,即 META-INF/spring.handlers 中会存放自定义标签相应的解析器,以 key=value 形式存放,其中 key 为 namespaceUri,value 为对应的解析器的全限定性类名,形式如下:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

这里就完成了 XmlReaderContext 的创建,接下来通过 documentReader 发起调用。

DefaultBeanDefinitionDocumentReader

protected void doRegisterBeanDefinitions(Element root) {this.delegate = createDelegate(getReaderContext(), root, parent);preProcessXml(root);parseBeanDefinitions(root, this.delegate);postProcessXml(root);this.delegate = parent;
}

核心逻辑:

  1. 创建 delegate,BeanDefinitionParserDelegate
  2. 真正的解析 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);}
}

根据是否为默认名命空间,将解析分为两个分支

  • parseDefaultElement
  • parseCustomElement

parseDefaultElement 

public boolean isDefaultNamespace(@Nullable String namespaceUri) {return !StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri);
}

默认的名命空间有两个条件

  • namespeceUri 不为 null 或 ""
  • 为:"http://www.springframework.org/schema/beans"
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele);}
}

 默认标签共四个:import、alias、bean、beans。

parseCustomElement

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}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));
}

核心逻辑:

先利用 XmlReaderContext 中创建的 DefaultNamespaceHandlerResolver 解析 namespaceUri,获取对应的 NamespaceHandler。

public NamespaceHandler resolve(String namespaceUri) {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);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);}}
}

核心逻辑:

调用 getHandlerMappings,加载 handlerMappingsLocation 下配置的 spring.handlers,得到 java.util.Properties,处理成 Map,赋值给 handlerMappings,接着根据 namespaceUri 从 handlerMappings 中去拿,第一次如果能拿到,为一个字符串,即配置的解析器的全限定性类名,加载这个类得到 Class,接着实例化一个 NamespaceHandler 对象,调用 init 方法,这样就完成了自定义解析器的创建。

此处有一个优化,就是类的加载和实例化只执行一次,创建完实例对象后会放入 handlerMappings,将原有的 value 值全限定性类名,替换为具体的实例对象,下一次遇到同样的namespaceUri 时,执行 resolve,直接从 handlerMappings 中查找到 NamespaceHandler 对象就返回了。

得到这个 NamespaceHandler,调用 parse 方法,完成具体对应标签的解析。

示例

以 aop 标签为例,下面来看看对应的 AopNamespaceHandler 及解析方法。先来看看这个标签下的元素名称。

public void init() {// In 2.0 XSD as well as in 2.5+ XSDsregisterBeanDefinitionParser("config", new ConfigBeanDefinitionParser());registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());// Only in 2.0 XSD: moved to context namespace in 2.5+registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

每个标签下,不同的元素名,匹配不同的 Parser,具体定义了几个 Parser,都在此处进行实例化创建,并将这些 BeanDefinitionParser 注册到父类 NamespaceHandlerSupport 的 parsers 中,方便后续使用。

这里有一点注意,就是实际扩展自定义标签,实现自定义标签 NamespaceHandler 时,并不需要直接实现 NamespaceHandler 接口,继承 NamespaceHandlerSupport 即可。

以 "aspectj-autoproxy" 为例。

<aop:aspectj-autoproxy/>

解析时,遇到这个标签,调用 parseCustomElement,接着拿到具体的 handler,发起 parse 调用,调入 NamespaceHandlerSupport#parse 中,根据解析出的节点名 aspectj-autoproxy,获取对应的 BeanDefinitionParser,此时为 AspectJAutoProxyBeanDefinitionParser。

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);extendBeanDefinition(element, parserContext);return null;
}
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);registerComponentIfNecessary(beanDefinition, parserContext);
}

创建 XmlReaderContext 时传入的 sourceExtractor 为 NullSourceExtractor,调用 extractSource 方法后返回 null。 

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());int requiredPriority = findPriorityForClass(cls);if (currentPriority < requiredPriority) {apcDefinition.setBeanClassName(cls.getName());}}return null;}RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);beanDefinition.setSource(source);beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);return beanDefinition;
}

可以看到,注册了一个名称为 AUTO_PROXY_CREATOR_BEAN_NAME 的 BeanDefinition。

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {if (sourceElement != null) {boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));if (proxyTargetClass) {AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));if (exposeProxy) {AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}
}

 判断标签中是否存在 proxy-target-class、expose-proxy 两个属性,存在,添加到 BeanDefinition 的 PropertyValues 中。其中 proxy-target-class 对应 ProxyConfig.proxyTargetClass 属性,控制在存在接口时,是否采用 Cglib 作为切面增强。expose-proxy 对应 exposeProxy 属性,控制在执行拦截方法 interceptor 时是否将当前 proxy 暴露出去,即是否执行AopContext.setCurrentProxy(proxy);。

private void extendBeanDefinition(Element element, ParserContext parserContext) {BeanDefinition beanDef =parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);if (element.hasChildNodes()) {addIncludePatterns(element, parserContext, beanDef);}
}private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {ManagedList<TypedStringValue> includePatterns = new ManagedList<>();NodeList childNodes = element.getChildNodes();for (int i = 0; i < childNodes.getLength(); i++) {Node node = childNodes.item(i);if (node instanceof Element) {Element includeElement = (Element) node;TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));valueHolder.setSource(parserContext.extractSource(includeElement));includePatterns.add(valueHolder);}}if (!includePatterns.isEmpty()) {includePatterns.setSource(parserContext.extractSource(element));beanDef.getPropertyValues().add("includePatterns", includePatterns);}
}

可以看到 extendBeanDefinition 方法的作用就是解析 <aop:aspectj-autoproxy> 的子标签 <aop:include name=""/>,最后将其作为 includePatterns 添加进 BeanDefinition 的 PropertyValues。

顺便说一句,dubbo 框架中的自定义标签 <dubbo:xxx> 就是自定义标签扩展的一个应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/852896.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

链路追踪-微服务小白入门(6)

背景 什么是链路追踪 随着微服务分布式系统变得日趋复杂&#xff0c;越来越多的组件开始走向分布式化&#xff0c;如分布式服务、分布式数据库、分布式缓存等&#xff0c;使得后台服务构成了一种复杂的分布式网络。在服务能力提升的同时&#xff0c;复杂的网络结构也使问题定…

inux下nvidia驱动安装-ubuntu22.04安装2060-notebook驱动

原文链接&#xff1a;linux下gcc编译安装与卸载-ubuntu22.04安装gcc-12.3.0 导言 nvidia驱动是显卡稳定运行的重要保证&#xff0c;不同的显卡有不同驱动&#xff0c;不同驱动对操作系统/cuda支持都存在一定差别。本次驱动安装主要完成2060-notebook显卡在linux系统下的驱动安…

ffmpeg封装和解封装介绍-(6)完成重封装mp4文件并截断10s~20s的视频并生成10s视频文件

源文件&#xff1a; #include <iostream> #include <thread> using namespace std; extern "C" { //指定函数是c语言函数&#xff0c;函数名不包含重载标注 //引用ffmpeg头文件 #include <libavformat/avformat.h> } //预处理指令导入库 #pragma …

Java语言+前端框架html+Thymeleaf +后端框架springboot开发的UWB智能定位系统源码 UWB三维可视化人员定位系统源码

Java语言前端框架htmlThymeleaf 后端框架springboot开发的UWB智能定位系统源码 UWB三维可视化人员定位系统源码 UWB定位系统基于B/S架构的软件和嵌入式硬件都具有很好的扩展性和兼容性&#xff0c;可以与其他系统接口&#xff08;比如&#xff1a;围界、AB门、高压电网、报警、…

对抗式生成模仿学习(GAIL)

目录 1 预先基础知识 1.1 对抗生成网络&#xff08;GAN&#xff09; 1.1.1 基本概念 1.1.2 损失函数 1.1.2.1 固定G&#xff0c;求解令损失函数最大的D 1.1.2.2 固定D&#xff0c;求解令损失函数最小的G 1.2 对抗式生成模仿学习特点 2 对抗式生成模仿学习&#xff08;…

【CS.PL】Lua 编程之道: 简介与环境设置 - 进度8%

1 初级阶段 —— 简介与环境设置 文章目录 1 初级阶段 —— 简介与环境设置1.1 什么是 Lua&#xff1f;特点?1.2 Lua 的应用领域1.3 安装 Lua 解释器1.3.1 安装1.3.2 Lua解释器的结构 1.4 Lua执行方式1.4.0 程序段1.4.1 使用 Lua REPL&#xff08;Read-Eval-Print Loop&#x…

计算机专业在未来的发展与抉择

目录 前言 计算机专业的发展历史 计算机专业的前景 计算机专业的挑战 如何判断自己是否适合计算机专业 计算机行业的未来发展态势 作为过来人和从业者 前言 随着2024年高考落幕&#xff0c;数百万高三学生又将面临人生中的重要抉择&#xff1a;选择大学专业。在这个关键…

鸿蒙Arkts上传图片并获取接口返回信息

需求&#xff1a; 选择相册图片后&#xff0c;将文件上传到服务器&#xff0c;接口会返回图片地址。 问题&#xff1a; 1、鸿蒙自带的文件上传返回值只会返回上传状态&#xff0c;不会返回接口返回信息。 类似问题 HarmonyOS上传文件以及权限授权_harmonyos中axios上传文件…

古诗词的魅力

目录&#xff1a; 一、换一种说法 二、宋词的男女对白 1、女问男答 2、男问女答 一、换一种说法 “独上高楼&#xff0c;望尽天涯路”的满心期盼。 “春风得意马蹄疾&#xff0c;一日看尽长安花"的志得意满。 “山重水复疑无路&#xff0c;柳暗花明又一村”的苦尽甘…

【Gradio】Building With Blocks 控制布局

默认情况下&#xff0c;块中的组件垂直排列。让我们看看如何重新排列组件。在底层&#xff0c;这种布局结构使用了网页开发的 flexbox 模型。 行 在 with gr.Row 子句中的元素将全部水平显示。例如&#xff0c;要并排显示两个按钮&#xff1a; with gr.Blocks() as demo:with g…

JAVA云HIS医院管理系统源码 云HIS运维平台源码 SaaS模式支撑电子病历4级,HIS与电子病历系统均拥有自主知识产权

JAVA云HIS医院管理系统源码 云HIS运维平台源码 SaaS模式支撑电子病历4级&#xff0c;HIS与电子病历系统均拥有自主知识产权 系统简介&#xff1a; SaaS模式Java版云HIS系统&#xff0c;在公立二甲医院应用三年&#xff0c;经过多年持续优化和打磨&#xff0c;系统运行稳定、功…

Unity C#中校对两个列表内数据是否正确

Unity C#中校对两个列表内数据是否正确 using System.Collections.Generic; using UnityEngine;public class ListComparer : MonoBehaviour {// 示例列表public List<string> list1 new List<string> { "apple", "banana", "cherry&qu…

SSH密钥认证:实现远程服务器免密登录的两种方法|Linux scp命令详解:高效实现文件与目录的远程传输

简介&#xff1a; 服务器之间经常需要有一些跨服务器的操作&#xff0c;此时就需要我们在一台服务器上登录到另外一台服务器&#xff0c;若是人为操作时我们都可以每次输入密码进行远程登录&#xff0c;但要是程序需要跨服务器时&#xff0c;每次输入密码就不现实了&#xff0c…

最新Sublime Text软件安装包分享(汉化版本)

Sublime Text 是一款广受欢迎的跨平台文本编辑器&#xff0c;专为代码、标记和散文编辑而设计。它以其简洁的用户界面、强大的功能和高性能而著称&#xff0c;深受开发者和写作者的喜爱。 一、下载地址 链接&#xff1a;https://pan.baidu.com/s/1kErSkvc7WnML7fljQZlcOg?pwdk…

docker 拉取镜像拉取超时的解决方法

错误提示&#xff1a; error pulling image configuration: download failed after attempts6: dial tcp 67.228.102.32:443: connect: connection refused latest: Pulling from freeradius/freeradius-server a8b1c5f80c2d: Retrying in 1 second 37ffe8884f09: Retryin…

Makefile并发执行

引言 最近在编译项目代码的时候&#xff0c;发现全量编译一次代码需要十分钟&#xff0c;加了多核编译参数之后&#xff0c;还会出现各种错误&#xff0c;导致编译失败。于是我就想改造下makefile文件&#xff0c;使其能够多核编译&#xff0c;经过改造之后&#xff0c;效果显…

解决:安装MySQL 5.7 的时候报错:unknown variable ‘mysqlx_port=0.0‘

目录 1. 背景2. 解决步骤 1. 背景 吐槽1&#xff0c;没被收购之前可以随便下载&#xff0c;现在下载要注册登录吐槽2&#xff0c;5.7安装到初始化数据库的时候就会报错&#xff0c;而8.x的可以一镜到底&#xff0c;一开始以为是国区的特色问题&#xff0c;google了一圈&#x…

[Algorithm][贪心][最长递增子序列][递增的三元子序列][最长连续递增序列][买卖股票的最佳时机][买卖股票的最佳时机Ⅱ]详细讲解

目录 1.最长递增子序列1.题目链接2.算法原理详解3.代码实现 2.递增的三元子序列1.题目链接2.算法原理详解3.题目链接 3.最长连续递增序列1.题目链接2.算法原理详解3.代码实现 4.买卖股票的最佳时机1.题目链接2.算法原理详解3.代码实现 5.买卖股票的最佳时机 II1.题目链接2.算法…

厂里资讯之总体架构介绍以及环境搭建

本项目是本人根据黑马程序员的微服务项目黑马头条进行包装改造&#xff0c;作为实习简历上面的项目&#xff0c;为了进一步熟悉深挖这个项目&#xff0c;写了这一系列的博客来加深自己对项目的理解。 概述 项目背景 本项目主要着手于使用户获取学校最新最热的资讯&#xff0c…

Django自定义CSS

创建一个CSS文件&#xff08;例如admin_custom.css&#xff09;&#xff0c;并在其中添加针对你希望修改的字段的CSS规则。在你的Django项目的settings.py文件中&#xff0c;添加自定义CSS文件的路径到STATICFILES_DIRS。 # settings.py STATICFILES_DIRS [ os.path.join(BA…