Spring解析之IoC:bean的加载(二)
前言
上一篇bean
加载的文章分析了bean
加载核心入口AbstractBeanFactory#doGetBean(String, Class, Object[], boolean)
的上半部分,该部分主要逻辑在显式调用getBean
时会被执行,更具体的来说是获得单例对象和通过自定义FactoryBean
创建对象时会执行上半部分;而下半部分会在初始化和获得prototype
多例对象时被执行,本文就是对这下半部分做深入分析
为了分析方便,我们再截取一次下半部分内容的代码,代码清单1
// (1)
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// (2)
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
// (3)
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// (4)
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// (5)
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
getBean(dependsOnBean);
registerDependentBean(dependsOnBean, beanName);
}
}
// (6)
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// (7)
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// (8)
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; " +
"consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
我们都知道对于scope = "prototype"
的对象来说,Spring是不会在真正使用该对象前创建它的,这一特性意味着Spring不能解决prototype
类型变量的循环依赖问题,道理很简单,当我们真正使用prototype
对象时肯定要求内部的依赖关系都建立完毕,而循环依赖的存在使这种对象间关系无法建立,必定就会抛出BeanCurrentlyInCreationException
上一篇文中说过,Spring使用“三级缓存”的形式能部分解决循环依赖问题,我们在这里就可以总结下“部分”指哪些情况:Spring能够解决singleton
下使用setter
方式形成的循环依赖问题,不能够解决singleton
下使用构造器形成的循环依赖,以及prototype
下的循环依赖。第一种能解决的原因是Spring解析之IoC:bean的加载(一)中分析过的缓存earlySingletonObjects
,当我们设置允许早期对象引用暴露allowEarlyReference
后,Spring会将通过构造器创建出来的,尚未调用setter
建立依赖关系的单例对象放入earlySingletonObjects
中,当我们需要一组存在循环依赖的对象时直接从里面取出每个对象手动建立依赖关系即可。但是如果是构造器形成的循环依赖,连创建早期对象都不可能,自然就没法解决循环依赖问题了
重温了Spring解决循环依赖的手段后看标注1,如果获取的是prototype
类型的对象且该对象正在创建中,必然就发生了循环依赖,直接抛出异常。标注2是存在父子容器时加载bean
的代码逻辑,如果大家用过SpringMVC相信对父子容器都不陌生,大多数情况下Web层的SpringMVC都作为Service层和Dao层Spring容器的子容器存在,子容器可以访问父容器中的bean
,反过来则不行,当子容器加载bean
时首先判断自身容器中是否存在同名的bean
,存在直接获得,不存在就去父容器中查找该名称的bean
标注2的逻辑就是这样,判断如果存在父容器,且当前容器中不存在beanName
对应的bean
就调用父容器parentBeanFactory
的getBean(String, Object...)
,又来了一个大循环。标注3中typeCheckOnly
表示获得bean
的目的是否是为了类型检查,而不是真正使用这个bean
,绝大部分情况我们当时是要用bean
啦,这时就要调用markBeanAsCreated(String)
将创建bean
的行为做记录,记录实际上就是将beanName
打上已经创建的标识放入Map<String, Boolean> alreadyCreated
中
标注4在Spring解析之IoC:bean的加载(一)中已经说过,如果<bean>
存在父子关系(注意不是容器的父子关系),getMergedLocalBeanDefinition(String)
会将父子关系的<bean>
信息融合在RootBeanDefinition
中
checkMergedBeanDefinition(RootBeanDefinition, String, Object[])
排除了两种情况下bean
的创建:1. <bean>
存在abstract
属性;2. scope = singleton
且参数args
有值的情况。第一种情况很好理解,都抽象了还创建毛线啊,而要想通第二种情况我们需要追根溯源看看args
来自哪里。显式调用getBean
获得对象有一个重载方法Object getBean(String, Object...)
,这里的args
就是第二个参数,该重载方法提供的目的是解决一个场景:初始化只存在有参构造的类,且参数要在获取时动态指定。创建只存在有参构造器的类很容易,直接<constrcut-arg>
指定就好,但是要每次创建的参数值不同再用该方法很明显就挂了啊,还是Object getBean(String, Object...)
好使。这也很好的解释了为什么args
不能和singleton
共存,因为args
说明可变性,singleton
说明唯一性,相互冲突
标注5涉及到一个之前没有讲过的<bean>
属性depends-on
,该属性的作用是让depends-on
内的对象先于<bean>
所代表的对象创建,但这两组对象并不要求有真正的依赖关系,我们举个例子,创建三个类Man
、Woman
和Family
,Family
配置depends-on
前两个类
图3. Woman
图4. Family
注意
Family
和Man
、Woman
只是组合关系并没有形成依赖,在XML进行配置如下图5. Man、Woman和Family相关配置
如果
depends-on
多个对象,多个对象之间可以用,
隔开,运行结果如下图6. depends-on例子运行结果
很明显Spring先初始化了
depends-on
的对象,至于多个depends-on
对象创建之间的顺序和XML中对应<bean>
书写顺序有关和depends-on
中的顺序无关,其实就是Spring自上而下解析标签的顺序。让我们再回到代码清单1,标注6、7、8三处很明显根据scope
的不同将处理逻辑分成了三块,为了分析清楚我们将每一块单独拎出来图7. 处理singleton对象逻辑
scope = singleton
对象的处理也分为三部分,1、2两部分是互有关联的,Object getSingleton(String, ObjectFactory)
第二个参数是接口ObjectFactory
的匿名实现,实现了Object getObject()
方法,具体的实现又调用了Object createBean(String, RootBeanDefinition, Object[])
,而该方法又是一个模板,真正的具体实现在AbstractAutowireCapableBeanFactory
中,我们先走进标注1看看做了什么图8. DefaultSingletonBeanRegistry的getSingleton(String, ObjectFactory<?>)
首先从缓存
singletonObejcts
中获取该单例对象,不存在进入创建流程,beforeSingletonCreation(String)
做创建对象前的处理工作,之后调用匿名实现的getObject()
进而调用上面说的模板方法createBean
创建对象,afterSingletonCreate(String)
做一些后处理操作,最后addSingleton(String, Object)
将创建的单例对象放入缓存图9. DefaultSingletonBeanRegistry的beforeSingletonCreation(String)
isCreationCheckExclusions
保存在创建时不需要做校验的bean
名称,singletonCurrentlyInCreation
大家应该很熟悉了,保存正在创建过程中的对象,整体逻辑就是,如果对象在创建时需要做校验(说明还没真正创建),但在正在创建对象的容器中又有它,那就说明有问题,抛出BeanCurrentlyInCreationException
。同时这一步也让大家知道了用于检测循环依赖的singletonCurrentlyInCreation
是什么时候被塞入内容的分析了这么多还在外围转悠,下面的
singletonFactory.getObject()
是创建bean
的核心代码了吧?是也不是,是是因为核心创建流程确实在该方法中,不是是因为小小的方法里面涉及的东东那多的啊,Spring的东西果然浩瀚如海啊。正因为这个问题的存在我想还是将核心逻辑再开一篇文章单独分析吧,要不然这篇文章得写多少啊,读者伤心写者流泪啊。我们现在只需知道singletonFactory.getObject()
主要得到的对象就两种:1.和<bean>
对应的真实bean
;2.创建bean
的自定义FactoryBean
实例,本篇文章先将外围逻辑都清理干净afterSingletonCreation(String)
闭着眼睛想都知道是创建单例之后的处理逻辑,和beforeSingletonCreation(String)
唯一不同的在于,后者是将正在创建的对象放入singletonsCurrentlyInCreation
,而前者是创建完对象后从singletonsCurrentlyInCreation
移除。addSingleton(String, Object)
逻辑也很简单图10. DefaultSingletonBeanRegistry的addSingleton(String, Object)
在Spring解析之IoC:bean的加载(一)中提到部分解决循环依赖的“三级缓存”,也说到过数据只能存在其中一个缓存中,这里就是当对象创建完成后将对象放入“一级”缓存中并删除其余缓存中的该对象流程,并在
registeredSingletons
已注册对象HashSet
中保存对应的beanName
回到图7,标注1、2都分析过了,标注3更好说了,同样在Spring解析之IoC:bean的加载(一)中已经进行了详细的分析,如果生成对象为
bean
直接返回,如果是自定义FactoryBean
,调用其实现的getObject()
创建bean
后返回,单例创建对象分析完毕,开始多例对象创建分析图11. 处理prototype对象逻辑
从宏观上看
scope = prototype
处理流程和单例时一样,围绕createBean(String, RootBeanDefinition, Object[])
进行前后校验处理,最后将可能的FactoryBean
转成特定的bean
返回图12. AbstractBeanFactory的beforePrototypeCreation(String)
prototypesCurrentlyInCreation
是一个ThreadLocal<Object>
变量,其中保存了当前线程正在创建的所有多例对象,只存在一个多例对象时ThreadLocal
内存的就是字符串beanName
,当有多个时内部存储的就是HashSet
集合,这里的存储又和本文最开始用prototypesCurrentlyInCreation
做多例类型循环依赖的判断对应上了。createBean(String, RootBeanDefinition, Object[])
和上面一样暂时跳过,afterPrototypeCreation(String)
思路和单例的后处理相似,将创建好的多例对象从prototypesCurrentlyInCreation
中移除,最后一步getObjectForBeanInstance(Object, String, String, RootBeanDefinition)
和之前分析的一模一样,不再赘述。最后一组是剩下所有scope
对象的处理逻辑,除了我们最常用的singleton
和prototype
外,针对Web
项目Spring又提供了request
、session
、global session
等其他类型(不同Spring版本scope
也不一样,到时大家看到多几个少几个不用诧异),此外我们还可以实现Scope
接口创建自定义的scope
,这里给一篇文章上面有Spring3.0相关scope
类型的用法讲解,大家可以拿来耍耍Bean scopes图13. 处理其他scope对象逻辑
其实其他
scope
类型创建bean
的逻辑从上图看和scope = “prototype”
相似,先从RootBeanDefinition
中得到配置的scope
,然后从Map<String, Scope> scopes
中得到该scope
对应的处理类,根据处理类中实现的Object get(String, ObjectFactory<?>)
,如果实现的逻辑中调用了ObjectFactory
的Object getObject()
,那就又回到了多例的处理逻辑,剩下的大家看前面的分析即可
后记
本文将getBean
中最后零碎的逻辑清理干净就是为了将核心创建bean
作为一个整体分析,即便如此由于Spring涉及的内容太多,createBean
依然比较庞大和杂乱,不管怎么说我们已经吹响了最后的冲锋号,年前必定攻下获得bean
这座山头,加油!