Spring源码深度解析第3章
Chapter3 默认标签的解析
Spring标签包括默认标签和自定义标签两种:
默认标签的解析是在parseDefaultElement函数中完成的,分别对4种标签(import、alias、bean和beans)做了不同的处理。
3.1 Bean标签的解析及注册
(1) 首先委托BeanDefinitionDelegate类的processBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如Class,name,id,alias之类的属性。
(2) 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
(3) 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
(4) 最后发出响应事件,通知想关的***,这个bean已经加载完成了。
3.1.1解析BeanDefinition
先从元素解析及信息提取开始,也就是BeanDefinitionHolder bdHolder=delegate.parseBeanDefinitionElement(ele),进入Delegate类的parseBeanDefinitionElement方法。对Spring的解析犹如洋葱剥皮一样,一层一层的进行,在对属性进行全面解析前,在外层又做了一个当前层的功能架构,当前层完成的主要工作如下:
(1)提取元素中的id和name属性 。
(2)进一步解析其他所有属性并统一封装到GenericBeanDefinition类型的实例中 。
(3)如果检测到bean没有指定beanName,那么使用默认规则为此bean生成beanName。
(4)将获取到的信息封装到BeanDefinitionHolder的实例中。
1. 创建用于承载属性的BeanDefinition
BeanDefinition是一个接口,在spring中此接口有三种实现:RootBeanDefinition、ChildBeanDefinition已经GenericBeanDefinition。而三种实现都继承了AbstractBeanDefinition,其中BeanDefinition是配置文件元素标签在容器中的内部表示形式。元素标签拥有class、scope、lazy-init等属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和中的属性一一对应。其中RootBeanDefinition是最常用的实现类,他对应一般性的元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务的。
在配置文件中可以定义父和字,父用RootBeanDefinition表示,而子用ChildBeanDefinition表示,而没有父的就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionResistry中读取配置信息。它们之间的关系如下图所示:
由此,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。而代码createBeanDefinition(className,parent)的作用就是实现此功能。
2. 解析各种属性
当创建好了承载bean信息的实例后,接下来就是解析各种属性了
3. 解析meta元素
在开始对meta元素解析分析前我们先简单回顾下meta属性的使用
<bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo">
<property name="beanName" value="bean demo1"/>
<meta key="demo" value="demo"/>
</bean>
这段代码并不会提现在demo的属性中,而是一个额外的声明,如果需要用到这里面的信息时可以通过BeanDefinition的getAttribute(key)方法获取,对meta属性的解析用的是:parseMetaElements(ele, bd)。
4. 解析lookup-method属性
元素lookup-method并不是很常用,但是在某些时候确实非常有用的属性,我们称他为获取器注入。获取器注入是一种特殊的方法注入,他把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。
5. 解析子元素replaced-method属性
replaced-method的用法,其主要功能是方法替换:即在运行时用新的方法替换旧的方法。与之前的lookup-method不同的是此方法不仅可以替换返回的bean,还可以动态的更改原有方法的运行逻辑,
6. 解析constructor-arg
对于constructor-arg子元素的解析,Spring是通过parseConstructorArgElements函数来实现的。函数内部使用了parseConstructorArgElement((Element) node, bd);函数,这个结构中遍历所有子元素,也就是提取所有constructor-arg,然后进行解析。
流程可以简单的总结为如下:
(1)首先提取index、type、name等属性
(2)根据是否配置了index属性解析流程不同
如果配置了index属性,解析流程如下:
(1)使用parsePropertyValue(ele, bd, null)方法读取constructor-arg的子元素
(2)使用ConstructorArgumentValues.ValueHolder封装解析出来的元素
(3)将index、type、name属性也封装进ValueHolder中,然后将ValueHoder添加到当前beanDefinition的ConstructorArgumentValues的indexedArgumentValues
对构造函数中属性元素的解析,经历了以下几个过程:
(1)首先略过decription和meta属性
(2)提取constructor-arg上的ref和value属性,并验证是否存在
(3)存在ref属性时,用RuntimeBeanReference来封装ref
(4)存在value属性时,用TypedStringValue来封装
(5)存在子元素时,对于子元素的处理使用了方法parsePropertySubElement(subElement, bd)。
7. 解析子元素properties
对于propertie元素的解析是使用的parsePropertyElements(ele, bd);方法
8.解析子元素qualifier
该元素的作用是,大多数时候都用注解的方式。
3.1.2 AbstractBeanDefinition属性
XML中所有的配置都可以在GenericBeanDefinition的实例类中找到对应的配置,GenericBeanDefinition只是子类实现,而大部分的通用属性都保存在了AbstractBeanDefinition中
3.1.3解析默认标签中的自定义标签元素
当对某个嵌套配置进行分析时,这里需要传递父类BeanDefinition,分析源码得知这里传递的参数其实是为了使用父类的scope属性,以备子类若没有设置scope时默认使用父类的属性,这里分析的是顶层配置,所以传递null。
程序首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler并进行进一步解析
总结下decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfRequired中对于程序默认的标签的处理其实是直接略过的,因为默认的标签到这里已经被处理完了,这里只对自定义的标签或者说对bean的自定义属性感兴趣。在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理器,并进行进一步的解析
3.1.4注册解析的BeanDefinition
在对配置文件解析完、装饰完后,接下来就是进行注册了,解析的BeanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中,而对于BeanDefinition的注册分成了两部分:通过beanName的注册以及通过别名的注册
1. 通过beanName注册BeanDefinition
bean的注册处理方式上,主要进行了几个步骤
· 对AbstractBeanDefinition的校验,此时的校验是针对AbstractBeanDefinition的methodOverrides属性的
· 对beanName已经注册的情况的处理,如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖
· 加入map缓存
· 清除解析之前留下的对应beanName的缓存
2. 通过别名注册BeanDefinition
注册alias的步骤如下:
· alias与beanName相同情况处理,若alias与beanName名称相同则不需要处理并删除原有的alias
· alias覆盖处理,若aliasName已经使用并已经指向了另一beanName则需要用户的设置进行处理
· 注册alias
3.1.5通知***解析及注册完成
通过代码getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)完成此工作,这里的实现只为扩展,当程序开发人员需要对注册BeanDefinition事件进行监听时可以通过注册***的方式并将处理逻辑写入***中
3.2 alias标签的解析
在对bean进行定义时,除了使用id属性来指定名称之外,为了提供多个名称,可以使用alias标签来指定。而所有的这些名称都指向同一个bean,在某些情况下提供别名非常有用, 比如为了让应用的每一个组件能更容易地对公共组件进行引用
然而,在定义bean时就指定所有的别名并不是总是恰当的,有时我们期望能在当前位置为那些在别处定义的bean引入别名,在XML配置文件中,可以用单独的<alias/>元素来完成bean别名的定义.
3.3 import标签的解析
applicationContext.xml文件中使用import的方式导入有模块配置文件,以后若有新模块的加入,可以简单修改这个文件,这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理
在解析import标签时,Spring进行解析的步骤大致如下:
· 获取resource属性所表示的路径
· 解析路径中的系统属性,格式如”${user.dir}”
· 判定location是绝对路径还是相对路径
· 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析
· 如果是相对路径则计算出绝对路径并进行解析
· 通知***,解析完成
3.4 嵌入式beans标签的解析
递归调用beans的解析过程