首页 > 编程语言 >(二)Spring源码解析:默认标签解析

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

时间:2023-05-23 11:04:12浏览次数:32  
标签:Spring ele bean 源码 标签 解析 方法

一、概述

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

(二)Spring源码解析:默认标签解析_配置文件

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

(二)Spring源码解析:默认标签解析_父类_02

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

二、bean标签的解析

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

(二)Spring源码解析:默认标签解析_父类_03

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

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

2.1> parseBeanDefinitionElement(ele)

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

(二)Spring源码解析:默认标签解析_父类_05

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

(二)Spring源码解析:默认标签解析_配置文件_06

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

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

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

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

(二)Spring源码解析:默认标签解析_父类_08

a> 创建GenericBeanDefinition实例

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

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

(二)Spring源码解析:默认标签解析_配置文件_09

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

(二)Spring源码解析:默认标签解析_父类_10

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

(二)Spring源码解析:默认标签解析_父类_11

通过上图源码,我们可以看出来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。在下面的源码解析中,我们会经常回过来参照这两个参数所存储的值。

(二)Spring源码解析:默认标签解析_父类_12

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

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

(二)Spring源码解析:默认标签解析_父类_14

c> 解析元数据

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

(二)Spring源码解析:默认标签解析_配置文件_15

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

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

(二)Spring源码解析:默认标签解析_配置文件_16

d> 解析lookup-method属性

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

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

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

(二)Spring源码解析:默认标签解析_配置文件_18

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

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

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

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

(二)Spring源码解析:默认标签解析_父类_20

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

e> 解析replaced-method属性

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

(二)Spring源码解析:默认标签解析_父类_21

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

(二)Spring源码解析:默认标签解析_配置文件_22

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

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

(二)Spring源码解析:默认标签解析_配置文件_23

f> 解析构造函数的参数

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

(二)Spring源码解析:默认标签解析_父类_24

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

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

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

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

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

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

(二)Spring源码解析:默认标签解析_配置文件_28

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

(二)Spring源码解析:默认标签解析_配置文件_29

(二)Spring源码解析:默认标签解析_父类_30

(二)Spring源码解析:默认标签解析_父类_31

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

(二)Spring源码解析:默认标签解析_配置文件_32

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

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

(二)Spring源码解析:默认标签解析_父类_34

g> 解析property子元素

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

(二)Spring源码解析:默认标签解析_配置文件_35

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

(二)Spring源码解析:默认标签解析_父类_36

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

h> 解析qualifier子元素

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

(二)Spring源码解析:默认标签解析_配置文件_37

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

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

(二)Spring源码解析:默认标签解析_父类_39

2.2> decorateBeanDefinitionIfRequired(ele, bdHolder)

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

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

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

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

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

(二)Spring源码解析:默认标签解析_配置文件_41

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

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

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

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

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

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

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

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

2.3> registerBeanDefinition(...)

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

(二)Spring源码解析:默认标签解析_父类_45

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

(二)Spring源码解析:默认标签解析_配置文件_46

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

(二)Spring源码解析:默认标签解析_配置文件_47

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

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

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

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

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

2.4> fireComponentRegistered(...)

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

(二)Spring源码解析:默认标签解析_配置文件_50

三、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)方法内实现的,具体源码请见下图所示:

(二)Spring源码解析:默认标签解析_配置文件_51

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

(二)Spring源码解析:默认标签解析_配置文件_52

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

四、import标签的解析

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

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

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

(二)Spring源码解析:默认标签解析_配置文件_53

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

(二)Spring源码解析:默认标签解析_配置文件_54

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

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

五、beans标签的解析

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

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

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

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」

标签:Spring,ele,bean,源码,标签,解析,方法
From: https://blog.51cto.com/u_15003301/6330115

相关文章

  • (三)Spring源码解析:自定义标签解析
    一、使用示例步骤1:创建User实体步骤2:定义一个XSD文件描述组件内容步骤3:创建BeanDefinitionParser接口的实现类,用来解析XSD文件中的定义和组件定义。步骤4:创建NamespaceHandlerSupport实现类,目的是将组件注册到Spring容器中。步骤5:编写spring.handlers和spring.schemas文件,默认位置......
  • LLvm 源码结构及测试基础
    LLvm源码结构及测试基础https://www.cnblogs.com/ainima/archive/2013/02/27/6331983.htmlhttps://www.cnblogs.com/ainima/archive/2013/02/27/6331985.htmlhttps://www.cnblogs.com/wujianming-110117/p/17128814.html......
  • Abp Vnext 动态(静态)API客户端源码解析
    根据以往的经验,通过接口远程调用服务的原理大致如下:服务端:根据接口定义方法的签名生成路由,并暴露Api。客户端:根据接口定义方法的签名生成请求,通过HTTPClient调用。这种经验可以用来理解ABPVNext自动API的方式,但如果不使用自动API并且控制器定义了路由的情况下,远程调用的路......
  • delphi RTC 解析statucode 问题
    开源的版本也一样会。rtcinfo.pas,以下的解析 StatusCode 有问题,如果服务器返回的是“HTTP/1.1200” 后面没有状态文本。会解析不到 状态码,这里应该按标准格式来,协议 空格 状态码 空格 状态文本一个一个解析才对,但是RTC 认为状态码、文本是要一起有的。例子:同一个请......
  • 聊聊如何利用spring插件来实现策略模式
    前言偶然的机会发现spring有个spring-plugin,官网对它的介绍是SpringPluginprovidesamorepragmaticapproachtoplugindevelopmentbyprovidingthecoreflexibilityofhavingpluginimplementationsextendingacoresystem'sfunctionalitybutofcoursenotdel......
  • CAN报文解析
    CAN报文解析 一、获得CAN报文的过程准备软硬件环境,硬件周立功CAN卡,软件使用ZLGCANTest,安装在电脑上;通过CAN卡连接整车CAN或者调试端口,通过软件交互界面获取CAN报文。二、CAN2.0B报文基本格式接收到的报文,是一串十六进制的字符,而报文格式定义和位数是按照二进制定义的。29位......
  • 微信小程序集成微信支付开发,后端是springboot项目
    一、准备工作首先是进入到小程序后台关联微信支付商户、以及接入微信支付申请 二、小程序端代码主要是用到了wx.requestPaymentAPI2.1在wxml文件中添加支付按钮<buttonbindtap="requestPayment">支付</button>2.2在wxss文件中定义支付按钮样式button{backgro......
  • drf——反序列化校验源码(了解)、断言、drf之请求和响应、视图之两个视图基类
    1.模块与包#模块与包 模块:一个py文件被别的py文件导入使用,这个py文件称之为模块,运行的这个py文件称之为脚本文件包:一个文件夹下有__init__.py#模块与包的导入问题'''1.导入模块有相对导入和绝对导入,绝对导入的路径是从环境变量开始的2.导入任何模块,如果......
  • SpringBoot配置文件
    概述初始化SpringBoot项目时,在resources目录下有一个默认的全局配置文件application.properties。SpringBoot通过配置文件来修改SpringBoot自动配置的默认值SpringBoot支持两种格式的配置文件application.yml和application.propertiesapplication.properties写法appl......
  • springmvc的流程及简单案例
    springmvc概念JavaEE体系结构包括四层,从上到下分别是应用层、Web层、业务层、持久层。Struts和SpringMVC是Web层的框架,Spring是业务层的框架,Hibernate和MyBatis是持久层的框架。SpringMVC是一种轻量级的、基于MVC的Web层应用框架,它属于Spring框架的一部分。SpringMVC说......