Spring04-如何解决循环依赖

读前猜想:

  1. 什么是Spring的循环依赖
  2. 如何检测是否存在循环依赖
  3. 如何解决循环依赖
  4. 循环依赖解决要满足哪些条件
  5. 什么是早期bean
  6. 为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?

什么是Spring的循环依赖

多个bean之间相互依赖,形成了一个闭环

1
2
3
4
5
6
7
8
9
10
11
@Component
public class A {
@Resource
private B b;
}

@Component
public class B {
@Resource
private A a;
}

如何检测是否存在循环依赖

​ 检测循环依赖比较简单,使用一个列表来记录当前正在创建的bean,在每个bean创建之前,先去记录中看一下自己是否在列表中,如果存在,则说明存在循环依赖(A->B->A),如果不在,则将其加入到列表中,待到bean创建完成,再从该列表中移除

单例:

spring创建单例bean时候,会调用下面方法

1
2
3
4
5
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

​ singletonsCurrentlyInCreation就是用来记录目前正在创建中的bean名称列表,this.singletonsCurrentlyInCreation.add(beanName)返回false,说明beanName已经在当前列表中了,此时会抛循环依赖的异常BeanCurrentlyInCreationException

多例

​ 以prototype情况为例,源码位于org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//检查正在创建的bean列表中是否存在beanName,如果存在,说明存在循环依赖,抛出循环依赖的异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}

//判断scope是否是prototype
if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
//将beanName放入正在创建的列表中
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//将beanName从正在创建的列表中移除
afterPrototypeCreation(beanName);
}
}

Spring如何解决循环依赖的问题

有了前面创建bean的基础后我们知道

Spring创建bean的主要步骤

1、实例化Bean,及调用构造器或者工厂方法创建bean

2、populateBean 填充属性,注入依赖的bean,比如通过set方式、@Autowired注解的方式注入依赖的bean

3、初始化bean,调用初始化方法

从Spring Bean创建的过程来看,注入依赖的方式有两种

  1. 实例化bean时通过构造方法注入
  2. 填充属性时

构造注入方式导致的循环依赖无法解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class ServiceA {
private ServiceB serviceB;

public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private ServiceA serviceA;

public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}

属性注入方式导致的循环依赖可以解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class ServiceA {
private ServiceB serviceB;

@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private ServiceA serviceA;

@Autowired
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
}

spring循环依赖数据流程图

1
2
3
4
5
6
7
8
9
10
1.spring轮询准备创建2个bean:serviceA和serviceB
2.spring容器发现singletonObjects中没有serviceA
3.调用serviceA的构造器创建serviceA实例
4.serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB
5.serviceA向spring容器查找serviceB
6.spring容器发现singletonObjects中没有serviceB
7.调用serviceB的构造器创建serviceB实例
8.serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA
9.serviceB向spring容器查找serviceA
10.此时又进入步骤2了

完整解决循环依赖的过程:

  • 1.从容器中获取serviceA
  • 2.容器尝试从单例缓存中找serviceA,找不到
  • 3.准备创建serviceA
  • 4.调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作
  • 5.将早期的serviceA暴露出去:即将其丢到第3级缓存**singletonFactories**中
  • 6.serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB
  • 7.容器尝试从单例缓存中找serviceB,找不到
  • 8.准备创建serviceB
  • 9.调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作
  • 10.将早期的serviceB暴露出去:即将其丢到第3级缓存singletonFactories中
  • 11.serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA
  • 12.容器尝试从单例缓存中找serviceA,发现此时serviceA位于第3级缓存中,经过处理之后,serviceA会从第3级缓存中移除,然后会存到第2级缓存中,然后将其返回给serviceB,此时serviceA通过serviceB中的setServiceA方法被注入到serviceB中
  • 13.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
  • 14.serviceB将自己返回给serviceA
  • 15.serviceA通过setServiceB方法将serviceB注入进去
  • 16.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除

循环依赖解决要哪些情况下无法解决

1、构造器注入(因为暴露早期对象是对象实例化后进行的)

2、A\B都是多例

3、A多例 B单例懒加载 且容器先获取A时(如果先获取B时可以解决循环依赖的)

4、某些情况下使用了在类方法上使用了@async注解的类(后续再讨论)

什么是早期bean

刚刚实例化好的bean就是早期的bean,此时bean还未进行属性填充,初始化等操作

为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?

为什么使用三级缓存

没看源码之前,有我们自己猜想,可能有一个 HashMap,缓存这我们创建的单例bean,每个创建完成后就往这个bean里放。

1
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

但是单单如此还解决不了问题,如以下场景

1
2
3
4
5
6
7
8
9
10
1.spring轮询准备创建2个bean:serviceA和serviceB
2.spring容器发现singletonObjects中没有serviceA
3.调用serviceA的构造器创建serviceA实例
4.serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB
5.serviceA向spring容器查找serviceB
6.spring容器发现singletonObjects中没有serviceB
7.调用serviceB的构造器创建serviceB实例
8.serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA
9.serviceB向spring容器查找serviceA
10.此时又进入步骤2

猜想

​ 既然要解决循环依赖,那么势必有一个Map可以缓存当前创建实例后的bean,以便别的bean在实例化时可以从这个map中获取对象。因此我们需要第二个Map:secondMap来帮我们保存正在创建的bean。

这样的话,我们猜想Spring创建Bean过程如下。

1
2
3
4
5
6
7
8
9
10
11
1.spring轮询准备创建2个bean:serviceA和serviceB
2.spring容器发现2个map中没有serviceA
3.调用serviceA的构造器创建serviceA实例,并且把serviceA实例添加到secondMap中
4.serviceA准备注入依赖的对象,发现需要通过setServiceB注入serviceB
5.serviceA向spring容器查找serviceB
6.spring容器发现2个map中没有serviceB
7.调用serviceB的构造器创建serviceB实例,并且把serviceB实例添加到secondMap中
8.serviceB准备注入依赖的对象,发现需要通过setServiceA注入serviceA
9.serviceB向spring容器查找serviceA,secondMap获取到serviceA
10.serviceB创建成功,添加到singletonObjects中,移除secondMap对应实例
10.serviceA创建成功,添加到singletonObjects中,移除secondMap对应实例

​ 咋一看,这样设计也不是不行,实际上,spring中也采用类似的方式,稍微有点区别,上面使用了一个缓存,而spring内部采用了3级缓存来解决这个问题,我们一起来细看一下。

3级缓存对应的代码:

1
2
3
4
5
6
/** 第一级缓存:单例bean的缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 第二级缓存:早期暴露的bean的缓存 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 第三级缓存:单例bean工厂的缓存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

因此bean从缓存角度实际的创建过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.从容器中获取serviceA
2.容器尝试从3个缓存中找serviceA,找不到
3.准备创建serviceA
4.调用serviceA的构造器创建serviceA,得到serviceA实例,此时serviceA还未填充属性,未进行其他任何初始化的操作
5.将早期的serviceA暴露出去:即将其丢到第3级缓存singletonFactories中
6.serviceA准备填充属性,发现需要注入serviceB,然后向容器获取serviceB
7.容器尝试从3个缓存中找serviceB,找不到
8.准备创建serviceB
9.调用serviceB的构造器创建serviceB,得到serviceB实例,此时serviceB还未填充属性,未进行其他任何初始化的操作
10.将早期的serviceB暴露出去:即将其丢到第3级缓存singletonFactories中
11.serviceB准备填充属性,发现需要注入serviceA,然后向容器获取serviceA
12.容器尝试从3个缓存中找serviceA,发现此时serviceA位于第3级缓存中,经过处理之后,serviceA会从第3级缓存中移除,然后会存到第2级缓存中,然后将其返回给serviceB,此时serviceA通过serviceB中的setServiceA方法被注入到serviceB中
13.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
14.serviceB将自己返回给serviceA
15.serviceA通过setServiceB方法将serviceB注入进去
16.serviceB继续执行后续的一些操作,最后完成创建工作,然后会调用addSingleton方法,将自己丢到第1级缓存中,并将自己从第2和第3级缓存中移除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//获取缓存
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//1.先从一级缓存中找
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//2.从二级缓存中找
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//3.二级缓存中没找到 && allowEarlyReference为true的情况下,从三级缓存中找
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//三级缓存返回的是一个工厂,通过工厂来获取创建bean
singletonObject = singletonFactory.getObject();
//将创建好的bean丢到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//从三级缓存移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
//创建完成后,添加到缓存中
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//将bean放入第1级缓存中
this.singletonObjects.put(beanName, singletonObject);
//将其从第3级缓存中移除
this.singletonFactories.remove(beanName);
//将其从第2级缓存中移除
this.earlySingletonObjects.remove(beanName);
}
}

解答

那回归正题,如上所述,为什么Spring要用3级缓存来解决循环依赖呢?如果只使用2级缓存,直接将刚实例化好的bean暴露给二级缓存出是否可以?

结论:不能

原因:这样做是可以解决:早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。

​ 若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。

举例说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Service1 {
public void m1() {
System.out.println("Service1 m1");
}

private Service2 service2;

@Autowired
public void setService2(Service2 service2) {
this.service2 = service2;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class Service2 {

public void m1() {
System.out.println("Service2 m1");
this.service1.m1();//@1
}

private Service1 service1;

@Autowired
public void setService1(Service1 service1) {
this.service1 = service1;
}

public Service1 getService1() {
return service1;
}
}

注意上面的@1,service2的m1方法中会调用service1的m1方法。

需求

​ 在service1上面加个拦截器,要求在调用service1的任何方法之前需要先输出一行日志:”你好,service1”

实现

新增一个Bean后置处理器来对service1对应的bean进行处理,将其封装为一个代理暴露出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

@Component
public class MethodBeforeInterceptor implements BeanPostProcessor {
@Nullable
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("service1".equals(beanName)) {
//代理创建工厂,需传入被代理的目标对象
ProxyFactory proxyFactory = new ProxyFactory(bean);
//添加一个方法前置通知,会在方法执行之前调用通知中的before方法
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("你好,service1");
}
});
//返回代理对象
return proxyFactory.getProxy();
}
return bean;
}
}

上面的postProcessAfterInitialization方法内部会在service1初始化之后调用,内部会对service1这个bean进行处理,返回一个代理对象,通过代理来访问service1的方法,访问service1中的任何方法之前,会先输出:你好,service1

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@ComponentScan(value = {"com.study.cycledepend.thirdlevelcache"})
public class AopConfig {

}
public class MainClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AopConfig.class);
context.refresh();
}
}

运行:报错了

可以看出是AbstractAutowireCapableBeanFactory.java:624这个地方整出来的异常,将这块代码贴出来给大家看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if (earlySingletonExposure) {
//@1
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//@2
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
//@3
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

我们再来通过代码级别的来解释上面代码:

​ 这段代码主要用来判断当有循环依赖的情况下,早期暴露给别人使用的bean是否和最终的bean不一样的情况下,会抛出一个异常。

@1:调用getSingleton(beanName, false)方法,这个方法用来从3个级别的缓存中获取bean,但是注意了,这个地方第二个参数是false,此时只会尝试从第1级和第2级缓存中获取bean,如果能够获取到,说明了什么?说明了第2级缓存中已经有这个bean了,而什么情况下第2级缓存中会有bean?说明这个bean从第3级缓存中已经被别人获取过,然后从第3级缓存移到了第2级缓存中,说明这个早期的bean被别人通过getSingleton(beanName, true)获取过

@2:这个地方用来判断早期暴露的bean和最终spring容器对这个bean走完创建过程之后是否还是同一个bean,上面我们的service1被代理了,所以这个地方会返回false,此时会走到@3

@3:allowRawInjectionDespiteWrapping这个参数用来控制是否允许循环依赖的情况下,早期暴露给被人使用的bean在后期是否可以被包装,通俗点理解就是:是否允许早期给别人使用的bean和最终bean不一致的情况,这个值默认是false,表示不允许,也就是说你暴露给别人的bean和你最终的bean需要是一直的,你给别人的是1,你后面不能将其修改成2了啊,不一样了,你给我用个鸟。

而上面代码注入到service2中的service1是早期的service1,而最终spring容器中的service1变成一个代理对象了,早期的和最终的不一致了,而allowRawInjectionDespiteWrapping又是false,所以报异常了。

如何解决

很简单,将allowRawInjectionDespiteWrapping设置为true就可以了,下面改一下代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//创建一个BeanFactoryPostProcessor:BeanFactory后置处理器
context.addBeanFactoryPostProcessor(beanFactory -> {
if (beanFactory instanceof DefaultListableBeanFactory) {
//将allowRawInjectionDespiteWrapping设置为true
((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
}
});
context.register(IocConfig.class);
context.refresh();

System.out.println("容器初始化完毕");
}

​ 上面代码中将allowRawInjectionDespiteWrapping设置为true了,是通过一个BeanFactoryPostProcessor来实现的,后面会有一篇文章来详解BeanFactoryPostProcessor,目前你只需要知道BeanFactoryPostProcessor可以在bean创建之前用来干预BeanFactory的创建过程,可以用来修改BeanFactory中的一些配置。

​ 这样配置虽然不报错了,但是spring容器中的Service1和Service2实例中的service1仍旧不是同一个对象,那么这种情况是不是很诧异,如何解决这个问题?

​ 既然最终service1是一个代理对象,那么你提前暴露出去的时候,注入到service2的时候,你也必须得是个代理对象啊,需要确保给别人和最终是同一个对象。

这个怎么整?继续看暴露早期bean的源码,注意了下面是重点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}

​ 我们可以自定义一个SmartInstantiationAwareBeanPostProcessor,然后在其getEarlyBeanReference中来创建代理不就可以了,聪明,我们来试试,将MethodBeforeInterceptor代码改成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//MethodBeforeInterceptor改为实现SmartInstantiationAwareBeanPostProcessor
@Component
public class MethodBeforeInterceptor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
if ("service1".equals(beanName)) {
//代理创建工厂,需传入被代理的目标对象
ProxyFactory proxyFactory = new ProxyFactory(bean);
//添加一个方法前置通知,会在方法执行之前调用通知中的before方法
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("你好,service1");
}
});
//返回代理对象
return proxyFactory.getProxy();
}

return bean;
}
}
public class MainClass1 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
Service1 service1 = context.getBean(Service1.class);
Service2 service2 = context.getBean(Service2.class);

service1.m1();
service2.m1();
System.out.println(service1.equals(service2.getService1()));
}
}
1
2
3
4
5
6
你好,service1
Service1 m1
Service2 m1
你好,service1
Service1 m1
true

这样就解决问题了。

猜想

1、@Async循环依赖如何解决?
2、SpringAOP、事务是否会导致早期暴露bean不一致?
3、开放性问题:为什么@EnableAsync不像AOP和事务一样实现AbstractAdvisorAutoProxyCreator(实现了SmartInstantiationAwareBeanPostProcessor)这个接口呢?异步事务按照AOP那样生效是不是就可以避免循环依赖呢?

1、标注了@Async注解方法的类A,在注入其他组件时B,@lazy,懒加载该类A,因此创建B时不会实时创建A,避免破坏早期bean,避免循环依赖

2、@EnableAspectJAutoProxy
-> AspectJAutoProxyRegistrar
->AnnotationAwareAspectJAutoProxyCreator
->AbstractAutoProxyCreator(实现了SmartInstantiationAwareBeanPostProcessor)(可在获取早期对象时就生成代理对象)

@EnableAsyncProxyAsyncConfiguration
->AsyncAnnotationBeanPostProcessor(没有实现SmartInstantiationAwareBeanPostProcessor)
(在postProcessAfterInitialization这个阶段才能触发生成代理对象)

3、不可估量