文章目录
- 一、引言
- 二、循环依赖出现的场景
- 2.1 有参构造导致的循环依赖问题
- 2.2 属性注入出现的依赖问题
- 2.3 Spring IOC创建Bean的流程
- 2.4 有参构造为何失败
- 2.5 属性注入为何能成功
- 2.6 AOP导致的循环依赖
- 三、Spring循环依赖源码刨析
- 四、Spring循环依赖案例刨析
一、引言
循环依赖
,顾名思义就是两个Bean
互相依赖导致出现的问题,我个人感觉有点死锁的味道,就是A需要实例化的B才能完成实例化,而B而又需要实例化的A才能完成实例化,从而出现了循环依赖问题。
那么Spring
是如何解决循环依赖问题的呢?相信这个问题的答案大家随口就能回答,但是我觉得知道理论的回答还不够,我认为还得亲自跟着源码看一遍Spring
到底是如何解决的才会印象深刻。
本篇文章会从理论和源码入手,先讲解循环依赖解决的理论部分,后面直接对源码DEBUG
,深入理解循环依赖问题。
PS:本篇文章中使用的
Spring
框架的版本是4.0.0.RELEASE
,不同版本之间源码会有一点点不同,但是大体逻辑差不多。另外,阅读本篇文章之前最好对
Spring IOC
和Spring AOP
的实现有一定了解,如果不太了解这两部分可以阅读一下两篇文章:
- 【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!
- 【Spring成神之路】老兄,来一杯Spring AOP源码吗?
二、循环依赖出现的场景
循环依赖的出现有两种场景,第一种是有参构造
导致的循环依赖问题,第二种是属性注入
导致的循环依赖问题,请看下面举的栗子
2.1 有参构造导致的循环依赖问题
先准备两个Bean,两个相互依赖
public class BeanA {
BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
BeanA beanA;
public BeanB(BeanA beanA){
this.beanA = beanA;
}
}
配置文件中Bean的注入使用构造器注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanA" class="spring.loop.BeanA">
<constructor-arg ref="beanB"/>
</bean>
<bean id="beanB" class="spring.loop.BeanB">
<constructor-arg ref="beanA"/>
</bean>
</beans>
测试类
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-loop.xml");
BeanA beanA = context.getBean(BeanA.class);
System.out.println(beanA);
}
运行代码
不出所料,Spring框架报错,提示当前请求的Bean真正创建中,是否存在循环依赖
2.2 属性注入出现的依赖问题
改用set
方法进行属性注入
public class BeanA {
BeanB beanB;
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
BeanA beanA;
public BeanA getBeanA() {
return beanA;
}
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanA" class="spring.loop.BeanA">
<property name="beanB" ref="beanB"/>
</bean>
<bean id="beanB" class="spring.loop.BeanB">
<property name="beanA" ref="beanA"/>
</bean>
</beans>
测试类
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-loop.xml");
BeanA beanA = context.getBean(BeanA.class);
BeanB beanB = context.getBean(BeanB.class);
System.out.println(beanA);
System.out.println(beanA.beanB);
System.out.println(beanB);
System.out.println(beanB.beanA);
}
}
运行代码发现,欸?!居然没报错?循环依赖居然成功完成了属性注入!!这到底发生了什么?
别急,看我后面慢慢道来。。。。。
2.3 Spring IOC创建Bean的流程
首先需要先大概了解Spring IOC创建Bean的流程,这里不会涉及源码,如果对源码感兴趣,请看这篇文章【Spring成神之路】一次SpringIOC源码之旅,女朋友也成为了Spring大神!
在Spring框架中,Bean的创建流程如下图所示
- 获取构造方法,并通过构造方法创建实例
- 实例化之后进行属性注入
- 执行初始化方法,包括前置通知、后置通知等
- 最后完成实例化
2.4 有参构造为何失败
由上图可见,调用BeanA
的有参构造方法实例化的时候需要BeanB
的实例,而实例化BeanB
则需要BeanA
的实例,两者形成了一个循环依赖,导致无法创建实例。
这个就好比老板需要你把这BUG改完才给你发工资,而你又要老板想给你发工资再改这个BUG,大家都不肯让步。
2.5 属性注入为何能成功
**那属性注入的方式,为何能解决循环依赖呢?**别急,同样的看下图
这里就比较巧妙了,就BeanA
来说,创建BeanA
的时候,会先调用BeanA
的无参构造,实例化一个未完成属性注入的BeanA
实例,这时候将这个半成品BeanA
放进缓存(earlySingletonObjects
,这种缓存也称为提前暴露缓存)中去。
然后BeanA
进行属性注入,属性注入需要BeanB
的实例,然后开始实例化BeanB
,调用BeanB
的无参构造方法,实例化了一个未完成属性注入的BeanB
,并将其放进缓存中去,BeanB
在属性注入阶段需要BeanA
的实例,于是会从缓存中获取BeanA
实例完成属性注入,接着执行处理初始化方法完成BeanB
的实例化。
有了BeanB
的实例后,BeanA
的创建自然就畅通无阻啦!
这时候就有大聪明就问了,在有参构造的时候我也弄个缓存不行么?
欸?还真不行喔,就上面这个有参构造例子来说,
BeanA
并不存在半成品之说,要想创建一个BeanA
,就必须有一个BeanB
的实例才可以,而创建BeanB
又需要一个BeanA
的实例。而属性注入这个例子中,
BeanA
和BeanB
都可以通过无参构造先创建一个实例,无参构造就意味着实例化一个半成品的BeanA
和BeanB
的时候不存在任何依赖(当然也不是一定要无参构造,有参构造只要参数不存在循环依赖即可),然后放进缓存供给BeanA
或BeanB
完成属性注入即可,这样循环依赖问题自然就迎刃而解啦
2.6 AOP导致的循环依赖
解决Spring的循环依赖问题,需要三级缓存:
- 一级缓存(
singletonObject
):所有创建好的单例Bean - 二级缓存(
earlySingletonObjects
):完成实例化但是未进行属性注入以及初始化的Bean实例 - 三级缓存(
singletonFactories
):提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂获取的对象
一级缓存、二级缓存很好理解,三级缓存到底是为了解决什么场景的问题的呢?
试想一下,如果我们创建的对象是一个代理对象,只用一级缓存和二级缓存能够解决循环依赖问题呢?
答案是不会出现循环依赖问题,但是创建出来的对象是不正确的!!!不正确的!!!不正确的!!!
假设
BeanA
是经过AOP代理的,BeanB
则是正常的Bean,BeanA
的实例创建好之后会放进缓存池中,接着创建BeanB
,BeanB
的创建完实例之后,进行属性注入,从缓存池中获取BeanA
的实例,将缓存池中BeanA
的引用赋值给BeanB
实例的beanA成员变量
BeanB
实例化后,BeanB
的实例引用会赋值给BeanA
的实例,完成属性注入后,BeanA
会进行AOP代理创建出代理对象,并将IOC容器中的beanA
的引用指向经过AOP代理后生成的BeanA
实例,也就是代理实例。可见,在没有三级缓存的情况下,
BeanB
实例中的beanA
成员变量的引用是beanA
的实例,而不是其代理类的实例,于是就会出现奇奇怪怪的问题。那怎么解决这个问题呢?
所以三级缓存的存在就是为了解决AOP代理,具体解决的AOP代理的循环依赖的流程如下图所示
区别就在于BeanB
的属性注入这里,如果BeanA
的实例化是一个AOP代理,则获取其代理对象,否则获取其半实例化对象。
当然,Spring的实现细节和这个图有所区别,但是大体思路差不多
三、Spring循环依赖源码刨析
PS:下面的源码解读会比较乱,就是看起来会感觉联系不起来,但是没关系可以先看一个眼熟,后面看我的流程图还有结合案例就能理解了
直接定位org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
这个方法
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
// 查询缓存中是否存在这个bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null){
// 省略部分代码
}else{
// 缓存中不存在,获取BeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 检查是否是单例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
}
在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
方法中,主要有两个流程:
- 从缓存中获取Bean
- 若缓存没有命中,则创建这个Bean
把目光集中在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
方法中去
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取Bean实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 一级缓存不存在这个Bean,且当前Bean处于创建中,于是从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 二级缓存中不存在这个Bean,获取锁,再从一二三级缓存中尝试获取实例
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 从三级缓存中回去
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 创建实例
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
这个方法的逻辑很简单,就是从一级、二级、三级缓存中获取Bean实例。在getSingleton
返回的不为null
,则经过一些处理后就会从doGetBean
返回。
如果从缓存中获取不到,会走org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 一级缓存不存在该Bean
beforeSingletonCreation(beanName);
boolean newSingleton = false;
// 创建单例Bean
singletonObject = singletonFactory.getObject();
newSingleton = true;
// 如果是一个单例则放进一级缓存
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 将bean放进一级缓存 二级缓存移除该bean
this.singletonObjects.put(beanName, singletonObject);
// 三级缓存移除该Bean
this.singletonFactories.remove(beanName);
// 二级缓存移除该Bean
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
getSingleton
方法的主要作用是从一级缓存中获取Bean,如果Bean不存在则创建缓存
singletonFactory.getObject()
实际调用的就是org.springframework.beans.factory.support.AbstractBeanFactory#createBean
方法
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
// 创建目标实例
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance;
}
先来看创建目标实例的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
方法吧
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 创建实例
Object bean = instanceWrapper.getWrappedInstance();
// 检查是否需要提前暴露这个Bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 如果需要提前暴露,则放进三级缓存中去
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 三级缓存中存储Bean
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
然后再看看org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
方法
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 检查Bean是否被动态代理,如果被动态代理需要返回代理对象
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// 创建代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
// 生成代理对象
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 放进二级缓存
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
四、Spring循环依赖案例刨析
PS:这一块需要结合第三部分的源码刨析以及以下流程图进行配合使用,否则会看得稀里糊涂!
就前面BeanA
与BeanB
循环依赖的案例来说,下面会一步一步刨析循环依赖的三级缓存是如何操作的。
在SpringIOC
刷新的时候,会调用org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
进行预处理单例Bean。
假设先初始化BeanA
,会调用org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
进行创建Bean。
一开始会先从调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
从三级缓存中进行获取BeanA
实例,这时候BeanA
实例肯定是不存在的。
接着调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
方法。
在这个方法中会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])
进行创建BeanA
实例。
实例创建完成后,会将这个BeanA
实例的工厂方法getEarlyBeanReference
添加进三级缓存。
getEarlyBeanReference
方法是一个获取代理对象的方法,如果前面实例化的BeanA
是代理对象,则会将这个对象进行动态代理,返回其代理对象,否则返回前面实例化的BeanA
实例。
这时候,三级缓存如下所示
然后BeanA
进行属性注入,尝试从SpringIOC
中获取BeanB
同理BeanB
也会这样被放进第三级缓存
BeanB
实例化完成后,会进行属性注入,从缓存中获取BeanA
实例
当从缓存中获取BeanA
实例的时候,会先执行org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
方法,这时候第三级的缓存始有BeanA
的,会拿到BeanA
的工厂Bean
,并调用其getObject
方法,也就是前面缓存的getEarlyBeanReference
方法获取BeanA
的半实例化对象,并放到二级缓存中去。
BeanB
获取道BeanA
未完成的实例进行完成属性填充后,会调用org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
将BeanB
在二三级的缓存移除,并将BeanB
的实例缓存进一级缓存。
这时候,BeanA
的实例化就能获取完成实例化的BeanB
进行输入注入,并缓存进一级缓存!
Spring循环依赖
的整体流程就是这样子了!