(二)Spring源码解析:默认标签解析

news2024/11/23 3:00:46

一、概述

还记得我们在上一讲末尾提到的关于默认标签解析自定义标签解析吧。本讲就来针对默认标签解析进行讲解。为了便于衔接上一讲的内容,我们将源码部分粘贴出来:

从上图中的源码中,我们可以看出默认标签的解析是在parseDefaultElement(ele, delegate)方法中实现的。我们来看一下这个方法如何实现的:

parseDefaultElement(ele, delegate)方法中我们可以看到,它分别针对4种不同的标签(即:import标签、alias标签、bean标签和beans标签)做了解析操作。那么下面我们就通过下面的4部分内容来对这些标签的解析进行深度剖析。

二、bean标签的解析

在上面的4种标签中,对bean标签的解析最为复杂和重要,所以我们先从这个标签开始深入分析,如果能够理解它的解析过程,那么其他标签就不难理解了。我们废话不多说,言归正传。先来看看processBeanDefinition(ele, delegate)方法内部的具体实现逻辑:

bean标签的解析和注册的时序图如下所示:

2.1> parseBeanDefinitionElement(ele)

首先我们来看一下元素解析部分内容,在parseBeanDefinitionElement(ele)方法中,但是这个方法其实只起到了“周转”的作用,它的内部其实又调用了parseBeanDefinitionElement(ele, null)方法,所以,我们看下一个方法的内部实现:

在 parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) 方法中,其实一共执行了如下4个步骤(【注】但是下图中只列出了其中的第1步骤和第2步骤,后面文章内容,再给大家展示剩下的后两个步骤)。

步骤1】提取Element元素中的“id”和“name”属性,并将name解析为aliases,然后为beanName赋值。
步骤2】解析其他属性并封装到GenericBeanDefinition类型的实例中。
步骤3】如果发现bean没有指定beanName,那么使用默认规则生成beanName
步骤4】将获取到的信息封装到GenericBeanDefinition类型的实例中。

2.1.1> 步骤1:解析beanName和aliases

其中,checkNameUniqueness(beanName, aliases, ele)是用于校验beanNamealiases是否是唯一的,即:不允许出现重复的名字(name)。如果发现有重复的,则直接抛出异常。具体逻辑实现,请见下图源码注释:

2.1.2> 步骤2:解析其他属性

对于其他属性的解析,是在parseBeanDefinitionElement(ele, beanName, containingBean)方法中实现的,具体源码如下所示:

a> 创建GenericBeanDefinition实例

创建BeanDefinition的这部分内容,是通过调用createBeanDefinition(className, parent)方法实现的。但是,在介绍整个方法内部逻辑之前,我们先来了解一下BeanDefinition,它到底是做什么用的?

BeanDefinition是配置文件中 <bean>元素标签在Spring容器中的表现形式 ,也就是说,它是用来承载bean信息的。在配置文件中可以定义父级<bean> 和 子集<bean> ,它们分别由RootBeanDefinitionChildBeanDefinition表示。而如果没有父级的话,则用RootBeanDefinition表示。而GenericBeanDefinition是从2.5版本之后加入进来的,用于为bean文件配置属性属性定义提供一站式服务。这三个类之间的关系如下图所示:

了解了BeanDefinition的3个实现类之后,我们再来看一下createBeanDefinition(className, parent)方法的具体实现:

上图中的createBeanDefinition(...)方法内部基本没做什么,关键的内容在红框的BeanDefinitionReaderUtils.createBeanDefinition(...)方法调用上,下图是createBeanDefinition(...)方法的源码部分:

通过上图源码,我们可以看出来createBeanDefinition(...) 方法执行了很简单的 4步 操作:

步骤1】创建GenericBeanDefinition实例对象bd
步骤2】为bd设置parentName属性。
步骤3】为bd设置beanClass属性。(如果classLoader不为空,则利用它去创建className对应的Class实例对象)
步骤4】为bd设置beanClassName属性。(如果classLoader为空,则只需赋值className即可)

b> 解析bean的各种属性

有了可以承载bean信息的GenericBeanDefinition实例对象之后,我们就来继续往下分析,看看负责 解析bean中各个属性 的逻辑代码——parseBeanDefinitionAttributes(ele, beanName, containingBean, bd):需要补充的一点是,此时入参containingBean等于null。

在分析该方法源码之前,我们先来看如下两个遍历中的内容:一个是入参的ele,另一个是全局变量defaults。在下面的源码解析中,我们会经常回过来参照这两个参数所存储的值。

针对parseBeanDefinitionAttributes(ele, beanName, containingBean, bd)方法的源码,如下图所示:

c> 解析元数据

下面我们在来看一下元数据解析方法——parseMetaElements(ele, bd);再介绍源码之前,我们先来看一下Spring中的meta标签使用方式如下:

从上面的例子我们可以看出来,使用了meta标签后,配置的desc并不会体现在Gun的属性当中,而只是一个额外的声明。当需要使用里面的信息的时候,可以通过BeanDefinitiongetAttribute(key)方法进行获取。

下面我们再来看一下parseMetaElements(ele, bd)的源码部分:

d> 解析lookup-method属性

我们平时对于lookup-method的使用其实是不多的,所以,我们在介绍关于lookup-method属性解析之前,先了解一下它是怎么使用的。如下代码所示,我们有一个抽象类Writer,它有一个写作的方法write(),但是具体使用哪种类型的笔去写作,则需要抽象方法getPen()来决定。那么关于笔的类型,我们提供了铅笔(Pencil)和毛笔(Brush)这两种。具体如下所示:

然后,我们通过在xml的配置文件中,使用lookup-method标签,将pencil的bean赋值给getPen()方法,那么运行结果显示“Pencil print !”

然后,我们可以通过修改xml配置中的lookup-method标签,将原来的“pencil”替换为“brush”,再运行一下,那么运行结果显示“Brush print !”

总结】根据上面的演示,我们可以知道lookup-method它的作用是获取器注入。即:获取器注入是一种特殊的方法注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。

好了,讲完了lookup-method的使用方法和作用之后,我们再来看一下parseLookupOverrideSubElements(ele, bd.getMethodOverrides())方法的源码实现:

总结parseLookupOverrideSubElements(ele, bd.getMethodOverrides())方法的内部逻辑跟我们解析元数据的方法parseMetaElements(ele, bd)非常类似。此次就不再赘述了。

e> 解析replaced-method属性

在介绍对replaced-method属性解析之前,我们还是来看一下它的使用场景吧。假设有个程序员Coder觉得工资又少,工作又多,非常烦躁。他打算去跟他们老板吼叫(shout)一番,表达自己内心的不满——“老板!我要离职!我不想写代码了!烦死了!”。具体实现如下所示:

但是在他马上要到达老板办公室门口的时候,接到了他女朋友的电话,电话那头说:“我爸妈同意咱俩结婚了,但是有个前提,就是要买个楼房。”程序员一想,自己买房的钱还没凑够呢!这不能离职啊!但是他此时已经推开了领导办公室的门。为了不喊出离职的那句话,我们可以采用MethodReplacer的方式改变shout方法的实现逻辑,从而让程序员Coder说出:“老板!我最爱写代码了!我会一直忠于公司为您工作的!”这句话,具体实现如下所示:

总结replaced-mothod可以实现方法替换,即:可以在运行时用新的方法替换现有的方法。

好了,理解了replaced-mothod标签的使用方式之后,我们来看一下parseReplacedMethodSubElements(ele, bd.getMethodOverrides())方法是如何对replaced-method属性进行解析的。详细解题源码如下所示:

f> 解析构造函数的参数

对于构造函数的配置方式,请见如下所示:

解释】默认情况下是按照参数的顺序注入的,当指定index索引后,就可以改变注入参数的顺序。

下面是parseConstructorArgElements(ele, bd)方法的源码实现:

我们可以在上面看到,方法内部又调用了parseConstructorArgElement((Element) node, bd)方法,这里面才是真正解析逻辑的地方:

上面的代码还是比较好理解的,但是关于parsePropertyValue(ele, bd, null)方法,我们还需要再看一下。源码如下所示:

那么关于上图蓝色框所标注的parsePropertySubElement(subElement, bd)方法,我们来看一下它的具体实现:

通过上图中源码的注释,可以看出逐一的对constructor-arg的子元素进行解析,针对每个子元素的解析此处不再进行讲解。这些子元素都可以通过如下配置方式进行配置:

g> 解析property子元素

在方法parsePropertyElements(ele, bd)中,对property标签进行了解析。关于property标签的使用,如下所示:

那么解析property子元素的源码如下所示:

解释】可以看到上面函数与构造函数注入方式不同的是,返回值使用PropertyValue进行封装,并记录在了BeanDefinitionpropertyValues属性里。

h> 解析qualifier子元素

当同一类型的bean注入到IOC之后,Spring容器中匹配的候选Bean数目必须有且仅有一个,那么此时,我们可以通过Qualifier指出注入Bean的名称,这样其一就消除掉了,配置方式如下所示:

具体源码实现,如下图所示:

2.2> decorateBeanDefinitionIfRequired(ele, bdHolder)

上面是针对parseBeanDefinitionElement(ele)方法进行的解析,下面我们要解析的是下图中红框标注的方法:

当Spring中的<bean>标签的子元素使用了自定义标签配置,则会被decorateBeanDefinitionIfRequired(ele, bdHolder)方法解析,如下所示:

<bean id="test" class="com.muse.Test">
   <mybean:user username="muse">
</bean>

那么,下面我们来看一下decorateBeanDefinitionIfRequired(ele, bdHolder)方法的源码实现:

解释】上面调用decorateBeanDefinitionIfRequired(ele, originalDef, null)方法的时候,第三个参数传递的是null,因为第三个参数是父类bean,当堆某个嵌套配置进行分析时,这里需要传递父类的BeanDefinition。分析源码得知这里传递的参数其实是为了使用父类的scope属性,以备子类若是没有设置scope时,默认使用父类的属性,这里分析的是顶层配置,所以传递null。

在上面代码中,我们看到无论是对所有属性还是所有子节点,都会执行decorateIfRequired(node, finalDefinition, containingBd)方法,那么我们再来看一下这个方法的内部实现:

其中,isDefaultNamespace(namespaceUri)是通过判断 namespaceUri不为空,并且等于"http://www.springframework.org/schema/beans",如果都满足,则是默认的命名空间。否则是自定义的命名空间。

通过调用readerContext.getNamespaceHandlerResolver(),我们可以获得如下红框中的 命名空间对应的处理器NamespaceHandler)。

其中关于自定义标签的解析过程,我们会在第3讲部分介绍,此处就直接略过了。

2.3> registerBeanDefinition(...)

上面我们执行完了对配置的解析装饰操作,那么下面就该到注册阶段了。涉及源码部分如下图所示:

在下面的代码中,我们可以看到总共有两个步骤的操作,分别是:注册BeanDefinition 和 注册别名Alias

那么下面我们先来看一下注册BeanDefinition的方法registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition())的处理逻辑:

解释】从上面的代码中,我们可以看到针对bean的注册处理方式上,主要进行了以下几个步骤:
步骤1】对AbstractBeanDefinition的校验。在解析XML文件的时候我们提过校验,但是此校验非彼校验,之前的校验是针对XML格式的校验,而此时的校验是针对于AbstractBeanDefinition的methodOverrides属性的。
步骤2】对beanName已经注册的情况的处理。如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖。
步骤3】加入map缓存。
步骤4】清除解析之前留下的对应beanName的缓存。

那么下面我们先来看一下注册别名Alias的方法registry.registerAlias(beanName, alias)的处理逻辑:

解释】由以上代码中可以得知,注册alias的步骤如下:
步骤1】alias与beanName相同情况处理。若alias与beanName名称相同,则不需要处理并删除掉原有alias。
步骤2】alias覆盖处理。若aliasName之前已经被配置了,则进行3个判断处理。
步骤3】alias循环检查。
步骤4】注册alias和beanName到aliasMap中。

2.4> fireComponentRegistered(...)

该方法的目的是为了通知监听器解析注册完成,这里的实现只为扩展,目前Spring并没有对其进行任何实现

三、alias标签的解析

在对bean进行定义时,除了使用id属性来指定名称之外,为了提供多个bean的名称,我们可以使用alias标签来指定。例如,通过在<bean>标签中设置name属性来为bean设置别名(alias)。如下所示:

<bean id="gun" name="m416, ak47" class="com.muse.Gun" />

另外,Spring还有另外一种声明别名的方式:

<bean id="gun" class="com.muse.Gun" />
<alias name="gun" alias="m416, ak47" />

关于alias标签的解析的代码,是在processAliasRegistration(ele)方法内实现的,具体源码请见下图所示:

processAliasRegistration(ele)方法的内部源码如下图所示:

解释processAliasRegistration(ele)这个方法的代码逻辑比较简单,与在上文的2.3中讲的内容一样,都是将别名alias与beanName组成一对注册到registry中。此处不再赘述。

四、import标签的解析

对于项目中的大量Spring配置文件而言,如果我们采取分模块维护,那么更易于我们的管理。我们可以通过采用<import>标签,来引入不同模块的配置文件,具体如下所示:

<beans>
    <import resource="order.xml" />
    <import resource="stock.xml" />
</beans>

关于import标签的解析逻辑,我们来看如下源码:

其中,importBeanDefinitionResource(ele)方法的详细源码内容和注释,如下图所示:

解释
1> 获取resource属性所表示的路径。
2> 解析路径中的系统属性,格式如“${user.dir}”。
3> 判定location是绝对路径还是相对路径。
4> 如果是绝对路径,则递归调用bean的解析过程,进行另一次的解析。
5> 如果是相对路径,则计算出绝对路径并进行解析。
6> 通知监听器,解析完成(Spring没有实现内部逻辑)。

五、beans标签的解析

对于嵌入式的beans标签,非常类似于import标签所提供的功能。具体源码位置为下图所示:

对于嵌入式beans标签来讲,并没有太多可讲,与单独的配置文件并没有太大的差别,无非是递归调用beans的解析过程。并且,在第1讲2.3.2章节,就对doRegisterBeanDefinitions(ele)方法进行了解析,此处就不在赘述了。

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

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

相关文章

Unit2_1:动态规划DP

文章目录 一、介绍二、0-1背包问题问题描述分析伪代码时间复杂度 三、钢条切割问题问题描述分析伪代码过程 四、矩阵链乘法背景性质分析案例伪代码 一、介绍 动态规划类似于分治法,它们都将一个问题划分为更小的子问题 最优子结构:问题的最优解包含子问题的最优解。DP适用的原…

一种单总线串口通信的调试方法

单总线的优点&#xff1a; 节省IO口&#xff0c;发送时可以将单片机的RXD设置为普通IO进行软件模拟发送&#xff0c;发送完设置为串口接收。避免通信干扰&#xff0c;由于是通过IO口对三极管/MOS管进行拉高拉低&#xff0c;外部信号不易对IO口进行干扰&#xff0c;EMI&#xf…

代码随想录算法训练营第四十六天 | LeetCode 139. 单词拆分、多重背包、背包总结

代码随想录算法训练营第四十六天 | LeetCode 139. 单词拆分、多重背包、背包总结 文章链接&#xff1a;单词拆分 多重背包 背包总结 视频链接&#xff1a;单词拆分 1. LeetCode 139. 单词拆分 1.1 思路 本题的那些单词就是物品&#xff0c;字符串就是背包&#xff0c;问用这些…

大数据学习之一文学会Spark【Spark知识点总结】

文章目录 什么是SparkSpark的特点Spark vs HadoopSparkHadoopSpark集群安装部署Spark集群安装部署StandaloneON YARN Spark的工作原理什么是RDDRDD的特点Spark架构相关进程Spark架构原理 Spark实战&#xff1a;单词统计Scala代码开发java代码开发任务提交 Transformation与Acti…

echarts 类目轴设置xAxis.interval不起效果

情景&#xff1a;在xAxis.typecategory&#xff1b;设置xAxis.interval不起效果&#xff1b; 解决方案&#xff1a;通过设置xAxis.axisLabel.interval属性

【H616_语言小美_控制安卓刷抖音项目 orangePi zero2 (已开源) 】.md uptada:23/11/07

文章目录 H616_语言小美_控制安卓刷抖音项目小美效果展示H616 ubuntu系统 安装adb智能公元 SU-03T 离线语音模组 固件制作配合串口实现 小美_控制安卓刷抖音 H616_语言小美_控制安卓刷抖音项目 注意&#xff1a;orangePi zero2 H616 安装系统为ubuntu 小美效果展示 语言小美 …

企业级私有化部署数字办公工具集合,解决企业全方位需求,快收藏

随着信息技术的日新月异&#xff0c;企业对IT建设的依赖程度越来越高。为了满足这一需求&#xff0c;软开企服凭借积累了多年的项目经验&#xff0c;推出了一款名为JVS的私有化部署企业信息化底座。JVS涵盖了多个产品领域&#xff0c;如协同办公、低代码开发、数据分析、风控决…

KaiOS APN配置文件apn.json调试验证方法(无需项目全编)

1、KaiOS 的应用就类似web应用&#xff0c;结合文件夹路径webapp字面意思理解。 2、KaiOS APN配置文件源代码在apn.json&#xff0c; &#xff08;1&#xff09;apn.json可以自定义路径&#xff0c;通过配置脚本实现拷贝APN在编译时动态选择路径在机器中生效。 &#xff08;…

linux复习笔记03(小滴课堂)

find命令&#xff1a; d查找目录&#xff1a; 按照文件权限查找&#xff1a; 600全部权限&#xff1a; -user根据所属主&#xff1a; 上面的例子是找出文件并打印有多少行。 我们也可以把我们查询到的结果复制到其它文件位置中去&#xff1a; 复制成功。 -mtime根据修改时间…

DOCTYPE是什么,有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别?

前言 持续学习总结输出中&#xff0c;今天分享的是DOCTYPE是什么&#xff0c;有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别。 DOCTYPE是什么&#xff0c;有何作用&#xff1f; DOCTYPE是HTML5的文档声明&#xff0c;通过它可以告诉浏览器&#xff0c;使用那个H…

MySQL的高阶语句

数据库的权限一般很小&#xff0c;工作中使用最多的场景就是查 排序、分组、子查询、视图、多表连接查询&#xff08;左连接、右连接、内连接&#xff09; create TABLE info ( id int(4) primary key, NAME varchar(5) not null, score decimal(5,2), address varchar(20)…

Django实战项目-学习任务系统-发送短信通知

接着上期代码内容&#xff0c;继续完善优化系统功能。 本次增加发送短信通知功能&#xff0c;学习任务系统发布的任务&#xff0c;为了更加及时通知到学生用户&#xff0c;再原有发送邮件通知基础上&#xff0c;再加上手机短信通知功能。 第一步&#xff1a;开通短信通知服务…

JSP通用材料收集归档系统eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 JSP 通用材料收集归档系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&#xff0c…

计算机服务器中了eking勒索病毒怎么办,勒索病毒解密,文件数据恢复

随着科技的不断发展&#xff0c;网络技术也得到了不断更新&#xff0c;极大地方便了人们的生产与生活&#xff0c;但随之而来的网络安全威胁也不断增加&#xff0c;近期&#xff0c;网络上的eking勒索病毒开始流行&#xff0c;它是phobos勒索家族中的一种病毒&#xff0c;具有较…

红黑树的性质和实现

红黑树 由于AVL树为了保持平衡需要经常旋转&#xff0c;开销是很大的。因此&#xff0c;红黑树比起AVL树放宽了平衡的限制。 概念 红黑树&#xff0c;在每个节点上增加一个存储位表示节点的颜色&#xff0c;可以是RED或BLACK。 通过对任何一条从根到叶子的路径上各个结点着…

EGpKa

利用数据增强的可解释图神经网络预测碳氢化合物的 pKa&#xff0c;原文 Explainable Graph Neural Networks with Data Augmentation for Predicting pKa of C–H Acids&#xff0c;代码在 DATA AND SOFTWARE AVAILABILITY。模型框架如下&#xff1a;

如何使用Linux DataEase数据可视化分析工具结合内网穿透实现远程办公

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

git从入门到会用

git从入门到大牛 什么是git理解版本控制版本控制软件 远程仓库git安装windows 安装git 客户端安装下载客户端安装界面介绍 客户端操作仓库管理创建本地仓库切换本地仓库删除本地仓库 文件操作创建文件修改文件删除文件新增/修改/删除 标志 分支管理创建分支切换分支合并分支 标…

【嵌入式 C 常用算法 3 -- 非线性存储结构 二叉树】

文章目录 树概念介绍树相关术语结点子树结点的度结点的层次有序树和无序树森林空树&#xff08;简单了解即可&#xff09; 二叉树二叉树性质满二叉树完全二叉树 二叉树的深度遍历前序遍历中序遍历后续遍历 二叉树的层次遍历二叉树的顺序存储结构二叉树的顺序存储结构C代码实现 …

如何解决DC电源模块的电源噪声问题

BOSHIDA 如何解决DC电源模块的电源噪声问题 在电子设备的设计和制作过程中&#xff0c;电源噪声是一个非常重要的考虑因素。DC电源模块的电源噪声问题是电子设备中普遍存在的问题之一。它不仅会影响设备的性能&#xff0c;还会对设备的寿命和稳定性产生负面影响。因此&#xf…