Spring如何解决循环依赖
作者:Grey
原文地址:Spring如何解决循环依赖
如果X这个类依赖了Y,Y这个类依赖了X,就产生了循环依赖。在普通Java(非Spring框架下),这并不是一个问题。
参考如下示例代码:
public class Demo {
public static void main(String[] args) {
X a = new X();
Y b = new Y();
a.y = b;
b.x = a;
System.out.println(a);
System.out.println(b);
}
}
class X {
Y y;
}
class Y {
X x;
}
但是Spring创建对象由于有相对复杂的生命周期,所以可能会导致循环依赖的问题,我们将如上代码转换成使用Spring的方式:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
public class CircularDependenciesDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 将当前类配置为Configuration类
applicationContext.register(CircularDependenciesDemo.class);
// 是否支持循环依赖
// applicationContext.setAllowCircularReferences(true);
// 启动
applicationContext.refresh();
System.out.println(applicationContext.getBean("x"));
System.out.println(applicationContext.getBean("y"));
// 关闭上下文
applicationContext.close();
}
@Bean
public static X x() {
return new X();
}
@Bean
public static Y y() {
return new Y();
}
}
class X {
@Autowired
Y y;
}
class Y {
@Autowired
X x;
}
运行,正常打印出:
git.snippets.fail1.X@ed9d034
git.snippets.fail1.Y@6121c9d6
以下是循环依赖是否支持的开关,如果设置为true,则支持循环依赖。
// 是否支持循环依赖
applicationContext.setAllowCircularReferences(true);
如果设置为false,再次运行main方法,则报错,以下为简略报错信息:
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean with name'x':
Requested bean is currently in creation:Is there an unresolvable circular reference?
通过上述实验,可以了解,Spring解决了循环依赖的问题,如何解决的呢?我们需要先看下Spring中Bean的生命周期:
以x的创建为例:
X阶段:
- 解析XML或者注解,将信息注册到BeanDefinition中
- 对象的实例化,可以理解成 X x = new X();
- x的属性填充(这里就涉及到要填充Y的实例)
- x的初始化
- Bean后置处理器进行处理(比如AOP)
- 把Bean添加到单例池中
在进行到第3步的时候,Spring会从单例池中找Y对应的Bean对象,如果找不到,则会执行y对象的创建过程生命周期,y也会经历和x一样的生命周期:
Y阶段:
- 解析XML或者注解,将信息注册到BeanDefinition中
- 对象的实例化,可以理解成 Y y = new Y();
- y的属性填充(这里就涉及到要填充x的实例)
- y的初始化
- Bean后置处理器进行处理(比如AOP)
- 把Bean添加到单例池中
这里执行到第三步的时候,需要找到x,但是x还没有进入单例池,所以,X阶段卡在第三步无法继续执行,Y阶段也卡在第三步无法继续执行,这就导致了循环依赖问题。
如何解决这个问题?
可以使用一个map,假设叫Tmap,在X阶段第二步的时候,将new出来的x放入这个Tmap中,这样一来,Y阶段的第3步涉及x的实例填充,就可以这样执行:
先从单例池中找x,找不到,再从map中找x,此时因为X阶段的第2步已经把new出来的x放入到Tmap中,所以可以从Tmap中找到,填充即可。 这样,Y阶段可以顺利执行完毕,然后X阶段正常结束
这样就解决了上面提到的循环依赖的问题。
但是会带来一个新的问题,我们用的这个Tmap存放的是X的原始对象,但是,有一种非常常见的情况是,X阶段的第5步,如果做了AOP,此时,会生成一个代理对象,假设叫XProxy,
那么我们需要放入Tmap中的其实是这个XProxy,而且填充Y中x属性也是用的XProxy对象,而非X对应的原始对象,所以刚刚我们使用Tmap这个方案就导致了新的问题:
在X阶段的第5步中,如果存在AOP,那么Y阶段的第3步处理的时候,如何正确的把X的代理对象填充到Y中?
如何解决这个问题呢?
可以考虑提前进行AOP,即:
在X阶段的第2步中,在new出X以后,提前进行AOP,生成代理对象,并且把代理对象放入Tmap中,这样,后续填充的时候,从Tmap中拿出的就是代理对象, 问题就解决了。
但是,新的问题又来了,正常的Bean的生命周期到第5步才进行AOP,怎么判断一个Bean需要提前AOP呢? 由上面的推论可知:只有出现了循环依赖且后置处理器中配置了AOP,才需要进行提前AOP,
否则,不需要提前进行AOP,简言之:如果没有出现循环依赖,就不需要提前AOP。
那么新的问题又来了,
问题1: 如何判断出现了循环依赖呢?
问题2: 如果已经进行了提前AOP,那么执行到第5步的时候,怎么判断已经执行过了AOP?
先看问题2,Spring中处理Aop的类是: AbstractAutoProxyCreator.java,其中定义了一个Map(earlyProxyReferences)来存已经提前进行了Aop的Bean, 关键代码如下:
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
回到第一个问题:如何判断出现了循环依赖呢?
我们可以在X阶段第2步中加入一个map,在X创建的时候,把x加入一个map,这个map表示正在创建的Bean。
在Y阶段的时候,判断需要X,先去单例池中找,找不到,然后去这个正在创建的map中找X,发现X找到,说明出现了循环依赖,所以需要提前AOP
但是,提前AOP的前提,是需要一个原始对象的,这个原始对象存在哪里? 这就是第三级缓存,即一个Map,其中Key是beanName, Value存的是lambda表达式
在提前AOP的时候,从三级缓存中拿到这个lambda表达式,执行,就拥有了原始对象(aop)
注意:需要提前aop的情况下,lambda表达式得到的是代理对象,不需要提前aop的情况下,得到的就是原始对象。
解决了如上问题,又会引入一个新问题,就是:如果新出现一个Bean,假设叫Z,也依赖X,且我们假设X已经配置了AOP,Z类的代码如下:
class Z {
@Autowired
X x;
}
执行Z阶段和执行Y阶段应该是类似的,在第2步判断的时候,都会判断需要进行提前AOP,且会通过三级缓存生成一个X的代理对象,此时Z阶段执行和Y阶段执行都会走这步,这样就导致了会生成两个X的代理对象,
如何保证只有一个代理对象呢? 那么二级缓存登场了,三级缓存生成的代理对象放入这个二级缓存中即可,下次从二级缓存中取出对应代理对象即可。
那么上述的代理对象什么时候放入单例池中呢?
Spring中是这样处理的,Bean先从单例池找,没有找到,二级缓存中找,找到了代理对象,就把代理对象放入单例池,删掉二级缓存中的代理对象即可。
最后,总的流程就是:
先从单例池找,找不到,去二级缓存找,再去三级缓存中找,三级缓存找到,会把对应记录删除,并把代理对象(如果有AOP的话)或者原始对象放入二级缓存。
二级缓存除了提高效率以外,最重要的作用是防止生成两个aop对象
正常情况下,二级三级缓存都没有用,二级三级缓存主要是为了解决循环依赖的问题。