前言
上一篇文章分析了Bean工厂的创建,其真正的实现类和核心为DefaultListableBeanFactory
,XML配置文件是封装成了Resource
,由XmlBeanDefinitionReader
进行了加载,最后在BeanDefinitionParserDelegate
解析类中将XML根元素<beans>
中的属性进行了处理。现在所有的准备工作完成,正式进入我们严格意义上的“有效”标签的解析
前面说到,默认情况下DefaultBeanDefinitionDocumentReader
对于XML的前处理和后处理都为空实现,真正的处理逻辑在parseBeanDefinitions(Element, DefaultBeanDefinitionDocumentReader)
中
两次判断红框内代码判断根元素和其下所有子元素是否处在默认名称空间定义内,来看一下怎么判断的
根据获得的节点名称空间判断如果为空或者等于常量
BEANS_NAMESPACE_URI
即为默认名称空间,该常量的值为http://www.springframework.org/schema/beans
,正是XML最基础的名称空间,回到图1,根据当前节点是否属于默认名称空间下分别调用红线处两个不同的方法,目前我们只分析默认名称空间下标签解析流程我们根据判断的四个常量可知,这里的默认标签元素分别为
<import>
、<alias>
、<bean>
和<beans>
处理
<bean>
大体可分为两步:1. 解析<bean>
;2. 注册bean
实例,与本文名称契合的这两部分并不是为了让大家更好的理解Spring运行流程特意杜撰的,而是Spring内部确实是这么划分的,上图中的两行代码就对应了这两个部分,我们先来第一部分<bean>
解析流程经过一段方法调用走到解析委派类的parseBeanDefinitionElement(Element, BeanDefinition)
中标注1解析
<bean>
的id和name属性,其中name属性可以配置多个,会将多个name转成alias数组。标注2判断id属性是否存在,如果存在赋值给beanName
,不存在用别名数组中第一个别名作为beanName
的值,标注3方法判断beanName
或者别名数组中的别名没有使用过,也就是说一个XML配置文件中同id或者同名不能重复注册也许有人产生疑问了,在第一篇文章中命名说到可以存在同名的
<bean>
,这里怎么又不可以了呢?其实答案在保存使用过的名称集合usedNames
中写的很清楚,在同一层次<beans>
下的所有<bean>
只能存在唯一不重复的ids/names
,但是如果有多个XML配置文件,两个相同的ids/names
出现在不同的配置文件中,这种情况是可以被允许的。初始化加载时usedNames
内没有内容,每一次<bean>
的解析都会向内存储对应的id/name
和所有的alias
。回到图5标注4创建出bean
的实例标注1将当前
<bean>
压入成员变量parseState
内部栈的栈顶,该对象用于记录解析到的一些重要标签或者属性对象,当解析发生错误时就可以知道到底解析到哪个对象出现了问题。标注2很明显是解析class
和parent
属性,其中的最后一句会生成AbstractBeanDefinition
的子类GenericBeanDefinition
,其中根据是否设置bean
的类加载器决定该类中的变量beanClass
保存的是className
还是类的实例。因为本文的例子中刚刚进入到解析XML和创建对应BeanFactory
的流程,此时类加载器为null
,为了证明这点我特意debug截图为证从上图可以很清楚的看到此时
classLoader = null
,那么创建的GenericBeanDefinition
中保存真实对象的变量beanClass
保存的实际上是类的名称而不是类的实例,记住这点非常关键否则下面没法分析图7标注3
parseBeanDefinitionAttributes(Element, String, BeanDefinition, AbstractBeanDefintion)
根据spring不同的版本解析诸如scope
、abstract
、lazy-init
等属性。标注4三个方法解析<meta>
、<lookup-method>
和<replaced-method>
子标签,其中<meta>
用于向BeanDefinition
中设置任意key-value
对;<lookup-method>
通常用于设置方法返回bean
的类型;<replaced-method>
使得一个类的某个方法可以被实现MethodReplacer
接口的类的reimplement(Object, Method, Object[])
代替。标注5中第一句就是大家熟悉的通过构造器实现注入的解析方式,对应的标签为<constructor-arg>
,代码清单1
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
// (1)
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// (2)
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
// (3)
this.parseState.push(new ConstructorArgumentEntry(index));
// (4)
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
// (5)
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
error("Ambiguous constructor-arg entries for index " + index, ele);
}
else {
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
else {
try {
// (6)
this.parseState.push(new ConstructorArgumentEntry());
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
this.parseState.pop();
}
}
}
我们可以根据参数的三种属性之一进行注入,第一种为下标,初始从0开始;第二种为根据参数类型,比如是java.lang.String
还是java.lang.Integer
,注意,对于基本数据类型是无法通过构造器注入的,需要转成基本数据类型的包装数据类型;第三种是通过参数的名称注入,这种没什么好说的。只有采用第一种基于下标的注入方式才会进入到if
中,标注3首先构建出一个Entry
的实现类ConstructorArgumentEntry
,并压入栈顶,这里的Entry
是变量parseState
的一个内部标记接口,只有实现该接口的对象才能被放入parseState
内部的栈内
标注4主要做了两件事:1. 处理<constructor-arg>
中对应的ref
和value
等属性;2. 解析<constructor-arg>
内部的子标签,具体流程我们来看一下
标注1解析出类型为
Element
,标签名不为description
也不为meta
的子标签,引用指向subElement
。标注2解析<constructor-arg>
的ref
和value
属性,确保两个属性不能同时出现。标注3得到ref
的值封装成RuntimeBeanReference
对象返回。标注4得到value
的值封装成TypeStringValue
返回。如果上面解析到<constructor-arg>
的子标签元素就会调用标注5的方法,该方法内包含一些大家可能还比较熟悉的标签,比如<list>
、<map>
等,感兴趣的读者可以继续往下深入,这里就不做分析了回到代码清单1看标注5处,上面说的在这个
if
中都是存在index
属性的,这里就是判断解析到的index
值是否已经存在,在ConstructorArgumentValues
对象中持有一个Map<Integer, ValueHolder>
集合,其中key
即为下标,value
为下标对应标签值封装的对象,如果通过验证就往map
中塞入对应的键值对。标注6处对应着else
的部分表示在没有index
属性时解析<constructor-arg>
的过程,同样调用parsePropertyValue(Element, BeanDefinition, String)
方法,封装ValueHolder
实例,根据属性类型的不同进行不同的设置,之前存在index
属性的标签对象存放在map
中,而不存在index
属性的标签对象放在链表中绕了一个大圈子终于分析完
<constructor-arg>
及其属性、子标签的解析流程,回到图7看标注5的第二句parsePropertyElements(Element, BeanDefinition)
,该方法和解析其他标签一开始的思路一致,都是遍历所有子标签,判断标签名称是否为property
,是则进入parsePropertyElement(Element, BeanDefinition)
首先获得
<Property>
必填属性name
的值,如若没有记录错误日志并抛出异常,对于一个BeanDefinition
来说,对象内存在一个MutablePropertyValues
对象,该对象中维护了一个List<PropertyValue>
集合,该集合就保存了所有的<property>
的name-value/ref
对,既然如此,在解析的过程中必然要判断集合中是否已经存在同名称的PropertyValue
,如果存在自然也会报错,不存在就需要真正解析<property>
,而解析的过程和解析<constructor-arg>
调用的方法一样,见图9,与前者解析唯一不同的地方在于第三个参数,对于<constructor-arg>
来说propertyName
为null
,而<property>
自然就是属性name
的值了,解析并创建PropertyValue
对象后,同样要使用parseMetaElement(Element, BeanMetadataAttributeAccessor)
解析内嵌的<meta>
标签,最后塞入List<PropertyValue>
中图7标注5中最后一句用来解析
<qualifer>
,在注解注入大行其道的编程界,相信也很少人使用这种方式进行注入操作,在这里就不展开分析了,关于几种注解注入的方式的源码解析后面会有单独文章分析。至此我们终于又回到了图5解析<bean>
大纲流程,看一看标注4和标注5之间的代码做了什么,如果我们在配置<bean>
时既没有填写id
,也没有填写name
,Spring会为我们生成一个name
,由于containingBean
在这里为null
,因此最终的流程会走到else
内,最终使用BeanDefinitionReaderUtils
来生成beanName
首先获得
BeanDefinition
中的class name
,因为上面的步骤已经解析过<bean class="">
,此时必然有值,再有第三个参数isInnerBean = false
(至于为什么请读者顺着流程走一次便知),因此最后形成返回值的公式即为while
中的generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
,其中的常量值为#
,举个例子,如果<bean class = "com.xiaomi.Student/>"
,那么最终生成的beanName
即为com.xiaomi.Student#0
。将解析的所有这一些按图5标注5代码封装成BeanDefinitionHolder
后标志着<bean>
全部解析完下面我们开始
Bean
实例的注册流程进入图3第二个下划线代码内,在正式分析之前我们需要先看几个重要的成员变量,第一篇文章中曾经说过BeanFactory
的核心实现类为DefaultListableBeanFactory
,在该类中有几个非常重要的成员变量先来说两个现在要用到的,
beanDefinitionMap
和beanDefinitionNames
,前者保存有id/beanName-bean实例
键值对,后者保存所有的beanName
注册
bean
的逻辑远远没有解析<bean>
的复杂,上图中的红框内即为核心代码。首先从beanDefinitionMap
根据beanName
查找是否存在对应的实例,如果存在判断是否开启了实例覆盖标识,没有开启抛出异常,再将beanName
和对应实例放入beanDefinitionMap
中,最后一句的作用是清除所有beanName
所属实例及其衍生类的本地缓存信息
后记
即便有意隐去的非重点流程,<bean>
解析及注册的调用关系依然很深,我们宏观上只需要记住经过本文对应的处理,Spring解析了所有XML配置文件,生成的bean工厂,但此时bean工厂中<bean>
对应的实例并没有真正创建。回想Spring解析之IoC:XML配置文件的加载及BeanFactory的创建中图5,Spring初始化的总纲,现在也才走到第二步,漫漫长路刚刚开始,继续努力吧!