Spring源码深度解析笔记(10)——默认标签的解析

之前提到过Spring中的标签包括默认标签和自定义标签,而这两种标签的用法以及解析方式存在着很大不同。
默认标签的解析是在parseDefaultElement函数进行的,函数中的功能逻辑一目了然,分别对4中不同标签(import、alisa、bean和beans)做了不同处理。

private void parseDefaultElement(Element ele,BeanDefinitionParserDetegate detegate)
// 对import标签的处理
importBeanDefinitionResource(ele);
// 对alisa标签的处理
proccessAlisaRegistration(ele);
// 对bean标签的处理
proccessBeanDefinition(ele,detegate);
// 对beans标签的处理0
doRegisterDefinition(ele);

3.1 bean标签的解析以及注册

在4种标签的解析中,对bean标签的解析最为复杂也最为重要,所以从此标签开始深入分析,如果能理解此标签的解析过程,其他标签的解析过程自然就迎刃而解,首先进入processBeanDefinition(ele,detegate)。

Spring源码深度解析笔记(10)——默认标签的解析_第1张图片

  1. 首先委托BeanDefinitionDetegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例包括配置文件的各个属性了,例如class、name、id、alisa之类的属性。
  2. 当返回bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
  3. 解析完成后,需要对解析后的bdHolder进行注册,同样注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
  4. 最后发出响应事件,通知想关的监听器这个bean已经加载完成了
3.1.1 解析BeanDefinition

首先从元素解析以及信息提取开始,也就是
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入BeanDefinitionDelegate类的parseBeanDefinitionElement方法。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele,BeanDefinition containingBean)
// 解析id属性
// 解析name属性
// 分割name属性

以上便是对默认标签解析的全过程了,当然,对Spring的解析犹如剥洋葱一样,一层一层地进行,尽管现在只能看到对属性id和name解析,但是很庆幸,思路我们已经了解了。在开始对属性展开全面解析前,Spring在外层又做了一个当前层的功能架构,在当前层完成的主要工作包括如下内容:

  1. 提取元素中的id以及name属性;
  2. 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中;
  3. 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName;
  4. 将获取到的信息封装到BeanDefinitionHolder的实例中。

进一步的查看步骤2中对其他标签其他属性的解析过程

publicAbstractBeanDefinition parseBeanDefinitionElement(Element ele,String beanName,BeanDefinition containBean)
// 解析class属性
// 解析parent属性
// 创建用于承载属性AbstractBeanDefinition类型的GeneriBeanDefinition
// 硬编码解析默认bean的各种属性
// 解析元数据
// 解析look-method属性
// 解析replace-method属性
// 解析构造函数参数
// 解析property子元素
/// 解析qualifier子元素

终于,bean标签的所有属性,不论常用的还是不常用的我们都看到了,尽管复杂的属性需要进一步解析,接下来,继续一些复杂标签属性的解析。

1. 创建用于属性承载的BeanDefinition

BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition三种均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件< bean>元素标签在容器中的内部表示形式。< bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和< bean>中的属性是一一对应的,其中RootBeanDefinition是最常用的实现类,它对应一般性的< bean>元素标签,GenericBeanDefinition是自2.5版本以后新加入到bean文件配置属性的定义类,是一站式服务类。

在配置文件中可以定义父< bean>和子< bean>,父< bean>用RootBeanDefinition表示,而子< bean>用ChildBeanDefinition表示,而没有父< bean>和子< bean>就用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。

Spring通过BeanDefinition将配置信息转换为容器内部标识,并将这些BeanDefinition注册到BeanDefinitionRegistry中,Spring容器的BeanDefinitionRegistry就像Spring配置信息的内存数据库,主要以map的形式保存,后续的操作直接从BeanDefinitionRegistry中读取配置信息。

Spring源码深度解析笔记(10)——默认标签的解析_第2张图片
由于可知,要解析属性首先要创建用于承载属性的实例,就是创建GenericBeanDefinition类型的实例,而代码createBeanDefinition(className,parent)的作用就是实现此功能。

2. 解析各种属性

当创建bean信息的承载实例后,并可以进行bean信息的各种属性解析,首先进入parseBeanDefinitionAttributes方法。parseBeanDefinitionAttributes方法对于element所有元素属性进行解析:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele,String beanName,BeanDefinition containBean,AbstractBeanDefinition bd)
// 解析scope属性
// 解析singleton属性
// 解析abstract属性
// 解析lazy-init属性
// 解析autowire属性
// 解析dependency-check属性
// 解析denpends-on属性
// 解析autowire-candidate属性
// 解析primary属性
// 解析init-method属性
// 解析destory-method属性
// 解析factory-method属性
// 解析factory-bean属性

3. 解析子元素meta

public void parseMetaElement(Element ele,BeanMetadataAttributeAccessor attributeAccessor)
// 获取当前节点的所有元素
// 提取meta
// 使用key、value构造BeanMetadataAttribute
// 记录信息

4 解析子元素lookup-method

子元素lookup-method似乎并不是很常用,但是在某些时候它的确是非常有用的,通常称它为获取器注入,引用《Spring in Action》中的一句话:获取器注入是一种特殊的方法注入,它是一个方法声明为放回某种类型的bean,但是实际要返回的bean是在配置文件里面配置的,此方法在涉及有些可插拔的功能上,解除程序依赖。

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides)
// 仅当在Spring默认bean的子元素下且为< lookup-method>时有效
// 获取要修饰的方法
// 获取配置返回的bean

上面的代码很眼熟,似乎与parseMetaElements的代码大同小异,最大的区别就是在if判断中的节点名称在这里被秀爱为LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用LookupOverride类型的实体进行数据承载并记录在AbstractBeanDefinition中的methodOverrides属性中。

5. 解析子元素replaced-method

这个方法主要是针对bean中replaced-method子元素的提取,方法替换:可以在运行时用新的方法替换现有的方法,与之前look-up不同的是,replace-method不但可以动态的替换返回实体bean,而且还能动态的更改原有方法的逻辑。

public void parseReplaceMethodSubElements(Element beanEle,MethodOverrides overrides)
// 仅当在Spring默认bean的子元素下且为< repalce-method>时有效
// 提取需要替换的旧方法
// 提取新的替换方法
// 记录参数

无论是look-up还是replace-method都是构造了一个MethodOverride,并记录在AbstractBeanDefinition中的methodOverrides属性中。

6. 解析子元素constructor-arg

对于constructor-arg子元素的解析,Spring通过parseConstructorArgElements函数来实现的

public void parseConstructorArgElements(Element beanEle,BeanDefinition bd)
// 解析constructor-arg子元素

这个结构遍历所有子元素,也就是提取所有的constructor-arg,然后进行解析,但是具体的解析却被安置在另一个函数parseConstructorArgElement中

public void parseConstructorArgElement(Element ele,BeanDefinition bd)
// 提取index属性
// 提取type属性
// 提取name属性
// 解析ele对应的属性元素
// 不允许重复指定相同参数
// 没有index属性则忽略去属性,自动寻找

上述过程中,首先是提取constructor-arg上的必要属性(index,type,name)。
如果配置了index属性,那么操作步骤如下:

  1. 解析constructor-arg子元素;
  2. 使用ConstructorArgumentValues.ValueHolder类型封装解析出来的元素;
  3. 将type、name、index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的indexedArgumentValues属性中;

如果没有指定index属性,那么操作步骤如下:

  1. 解析constructor-arg子元素;
  2. 使用ConstructorArgumentValues.ValueHolder类型封装解析出来的元素;
  3. 将type、name、index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的genericArgumentValues属性中;

可以看出,对于是否定制index属性来讲,Spring的处理流程是不同的,关键在于属性信息的保存位置。
了解了整个流程后,进一步了解解析构造函数配置中子元素的过程,进入parsePropertyValue:

public Object parsePropertyValue(Element ele,BeanDefinition bd,String propertyName)
// 一个属性只能对应一种类型:ref、value、list等
// 解析constructor-arg上的ref属性
// 解析constructor-arg上的value属性
// ref属性的处理,使用RuntimeBeanReference封装对应的ref名称
// value属性的处理,使用TypedStringValue封装
// 解析子元素
// 既没有ref也没有value也没有子元素时抛出异常

从代码上看,对构造函数中属性元素的解析,经历了一下几个过程:

  1. 略过description或者meta;
  2. 提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在constructor-arg上不存在以下情况。
    2.1 同时既有ref属性又有value属性
    2.2 存在ref属性或者value属性且又有子元素
  3. ref属性的处理,使用RuntimeBeanReference封装对应的ref名称;
  4. value属性的处理,使用TypedStringValue封装;
  5. 子元素的处理。

对于子元素的处理,parsePropertySubElement中实现了各种元素的分类处理:

public Object parsePropertySubElement(Element ele,BeanDefinition bd,String defaultValueType)
// 解析local
// 解析parent
// 对ideref元素的解析
// 对value子元素的解析
// 对null子元素的解析
// 对array子元素的解析
// 对list子元素的解析
// 对set子元素的解析
// 对map子元素的解析
对props子元素的解析

可以看出,上面的函数实现了所有可支持的子类的分类处理。

7. 解析子元素property

parsePropertyElement函数完成了对property属性的提取,具体的解析过程如下:

public void parsePropertyElement(Element ele,BeanDefinition bd)
// 获取配置元素中的name的值
// 不允许多次对同一属性配置

可以看出上面的函数与构造函数注入的方式不同的是将方绘制使用PropertyValue进行封装,并记录在BeanDefinition中的propertyValues属性中

8. 解析子元素qualifier

对于qualifier元素的获取,接触更多的是注解的形式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选Bean数目必须有且仅有一个,当找找不到一个匹配的Bean时,Spring容器将会抛出BeanCreationException异常,并指出必须至少拥有一个匹配的Bean。

Spring允许通过Qualifier指定注入Bean的名称,这样就消除歧义了。

3.1.2 AbstractBeanDefinition属性

至此便完成了对XML文档到GenericBeanDefinition的转换,也就是到这里,XML中所有配置都可以在GenericBeanDefinition的实例中找到对应的配置。

GenericBeanDefinition只是子类实现,而大部分通用属性都保存在了AbstractBeanDefinition中。

public abstract class AbstractDeanDefinition extends BeanMetadateAttributeAccessor implements BeanDefinition,Cloneable{
// bean的作用范围,对应bean属性的scope
// 是否是单例,来自bean属性scope
// 是否是原型,来自bean属性scope
// 是否是抽象,来自bean属性abstract
// 是否延迟加载,来自lazy-init
// 自动注入模式,对应bean属性autowire
// 依赖检查,Spring 3.0后弃用这个属性
// 用来表示一个bean的实例化靠另一个bean先实例化,对应bean属性depend-on
// autowire-candidate属性设置为false,这样容器在查找自动装配对象时,将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,但是该bean还是可以使用自动装配来注入其他bean的。对应bean属性autowire-candidate
// 自动装配时当出现多个bean候选者时,将作为首选者,对应bean属性primary
// 用于记录Qualifier,对应子元素Qualifier
// 允许访问非公开的构造器和方法,程序设置
// 是否以一种宽松的模式解析构造函数
// 记录构造函数注入属性,对应bean属性constructor-arg
// 普通属性集合
// 方法重写的持有者,记录lookup-method、replace-method元素
// 对应bean属性factory-bean
// 对应bean属性factory-method
// 初始化方法,对应bean属性init-mothod方法
// 销毁方法,对应bean属性destory-method方法
// 是否执行init-mothod方法,程序设置
// 是否执行destory-method方法,程序设置
// 是否是用户定义的而不是应用程序本身定义的,创建AOP时候为true,程序设置
// 定义bean的应用,APPLICATION:用户;INFRASRUCTRUE:完全内部使用,与用户无关;SUPPORT:某些复杂配置的一部分,程序设置
// bean的描述信息
// 这个bean定义的资源
}

3.1.3 解析默认标签中的自定义标签元素

我们已经用了大量的篇幅分析了BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele)这句代码,接下来,要进行bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder)代码的分析。

当Spring中的bean使用的是默认的标签配置,但是子元素却使用了自定义的配置时,这句代码便会起作用。

public BeanDefinitionHolder decorateBeanDefinitionIfRequire(Element ele,BeanDefinitionHolder definitionHolder,null)

这里将函数的第三个参数设置为空,那么第三个参数是做什么用的呢?什么情况下不为空呢?其实第三个参数是父类bean,当某个嵌套配置进行分析时,这里需要传递父类beanDefinition。分析源码的遏制这里传递参数其实是为了使用父类的scope属性,以备子类若没有设置scope是默认使用父类的属性,这里分析的是顶层配置,所以传递null,将第三个函数设置为空后进一步跟踪函数:

public BeanDefinitionHolder decorateBeanDefinitionIfRequire(Element ele,BeanDefinitionHolder definitionHolder,null)
// 遍历所有的属性,看看是否有适用于修饰的属性
// 遍历所有的子节点,看看是否有适用于修饰的子元素

上面的代码,可以看到函数分别对元素的所有属性以及子元素进行了decorateIfReuired函数的调用

private BeanDefinitionHolder decorateIfRequired(Node node,BeanDefinitionHolder originalDef,BeanDefinition containingBd)
// 获取自定义标签的命名空间
// 对于非默认标签进行修饰
// 根据命名空间找到对应的处理器
// 进行修饰

程序走到这里,调理其实非常请处理了,首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所有对应的NamespaceHandler进行进一步解析。

总结一下decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfDefinitionIfRequired中可以看出对程序默认标签的处理其实是直接略过的,因为默认的标签到这里已经处理完了,这里只对自定义标签或者bean的自定义属性感兴趣。在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理器,并进一步解析。

3.1.4 注册解析的BeanDefinition

对于配置文件,解析是解析完了,装饰也装饰完了,对于得到的beanDefinition已经可以满足后续的使用要求了,为剩下的工作就是注册了,也就是processBeanDefinition函数中的BeanDefinitionReaderUtils.registryBeanDefinition(bdHolder,getReaderContext().getRegistry())代码的解析了。

public static void registryBeanDefinition(BeanDefinitionHolder definitionHolder,BeanDefinitionRegistry registry)
// 使用beanName做唯一标识注册
// 注册所有的别名

从上面代码可以看出,解析的beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中,而对于beanDefinition的注册分为两部分,通过beanName的注册以及通过别名的注册。

1. 通过beanName注册BeanDefinition

对于beanName的注册,或许许多人认为的方式就是将beanDefinition直接放入map中就好了,使用beanName作为key,确实,Spring就是这么做的,只不过除此之外,它还做了点别的事。

public void registryBeanDefinition(String beanName,Definition beanDefinition) throws BeanDefinitionStoreException
// 注册前的最后一次校验,这里的校验不同于之前的XML文件校验,主要是对于AbstractBeanDefinition属性中的methodOverrides校验,校验methodOverrides是否与工厂方法并存或者methodOverrides对应方法根本不存在
// 因为beanDefinitionMap是全局变量,这里肯定会存在并发访问的情况
// 处理注册已经注册的beanName情况
// 如果对应的BeanName已经注册且在配置中配置了bean不允许被覆盖,则抛出异常
// 记录beanName
// 注册beanDefinition
// 重置所有beanName对应的缓存

上面的代码可以看出,对于bean的注册处理方法上,主要进行了几个步骤:

  1. 对AbstractBeanDefinition的校验。在解析XML文件的时候,是针对XML格式的校验,而此时的校验时是对于AbstractBeanDefinition的methodOverrides属性的;
  2. 对beanName已经注册情况的处理。如果没有设置不允许bean的覆盖,则需要抛出异常。
  3. 加入map缓存
  4. 清除解析之前留下的对应beanName的缓存
2. 通过别名注册BeanDefinition

在理解了注册bean的原理之后,理解别名注册的原理就容易多了

public void registryAlias(String name,String alisa)
// 如果beanName与alisa相同的话不记录alisa,并删除对应的alisa
// 如果alisa不允许被覆盖则抛异常

由以上代码中可以得知注册alisa的步骤如下:

  1. alisa与beanName相同情况处理,若alisa与beanName并名称相同则不要处理并删除原有alisa;
  2. alisa覆盖处理,若alisaName已经使用并已经指向了另一beanName则需要用户的设置进行处理;
  3. alisa循环检查,单A->B存在时,若再次出现A->C->B的时候则会抛出异常;
  4. 注册alisa。
3.1.5 通知监听器解析及注册完成

通过代码getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此项工作,这里的实现只为扩展,当程序开发人员需要注册BeanDefinition时间进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前Spring中并没有对此事件做任何逻辑处理。

3.2 alisa标签的解析

通过上面较长的篇幅终于分析完了默认标签中对bean标签的处理,那么之前提到过的,对配置文件的解析保罗import标签、alisa标签、bean标签、beans标签的处理,现在已经完成了bean标签的解析,其他的步骤也都是围绕着第3个解析进行的。在分析了第3个解析步骤后,在回过头来看看对alisa标签的解析。

在对bean进行定义时,除了使用id属性来指定名称之外,为了提供对个名称,可以使用alisa标签来指定,而所有的这些名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用的每一个组件能更容易的对公共组件进行引用。

然而,在定义bean是就指定所有的别名并不是总是恰当的。又是期望能在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可用单独的< alisa/>元素来完成bean别名的定义。

在之前的章节已经讲过对于bean中name元素的解析,那么再来深入分析下alisa标签的解析过程

processAlisaRegistation(Elemet ele)
// 获取beanName
// 获取alisa
// 注册alisa
// 别名注册后通知监听器做相应处理

3.3 import标签的解析

对于Spring配置文件的编写,分模块是大多数人能想到的方法,使用import是个好办法,applicationContext.xml文件中使用import的方法导入有模块配置文件,以后如果有新的模块的加入,那就可以简单修改这个文件了,这样可以大大简化了配置后期维护的复杂度,并使配置模块化,易于管理,import标签的解析方法:

protected void importBeanDefinitionResource(Element ele)
// 获取resource属性
// 如果不存在resource属性在不做处理
// 解析系统属性,格式如:“${user.dir}”
// 判定location是绝对URI还是先对URI
// 如果是绝对URI则直接根据地址加载对应的配置文件
// 如果是相对地址则根据相对地址计算出绝对地址
// Resource存在多个子类实现,而每个resource的createRelative方法都不一样,所以先使用子类的方法进行解析
// 如果解析不成功,则使用默认解析器ResourcePatternResolver进行解析
解析后进行监听器激活处理

上面的代码不能,大致流程如下:

  1. 获取resource属性所表示的路径;
  2. 解析路径中的系统属性;
  3. 判定location是绝对路径还是相对路径;
  4. 如果是绝对路径则递归调用bean的解析过程,进行另一次解析;
  5. 如果是相对路径则计算绝对路径并进行解析;
  6. 通知监听器,解析完成。

3.4 嵌入式beans标签的解析

对于嵌入式beans标签,非常类似于import标签所提供的功能,并没有太多可讲,与单独配置文件并没有太大的差别,无法是递归调用beans的解析过程。

你可能感兴趣的:(笔记)