
了解专栏完整的内容,请点击跳转:
Spring framework专栏导航页
上一篇文章我们了解了Spring Bean的生命周期,当时提到了Bean的循环依赖问题,本文就再次展开描述该问题,看Spring是如何解决循环依赖的。
解决循环依赖流程图
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
如上述代码所示,就是循环依赖。其实在我们手动去new对象的时候,这根本不是什么问题,为什么呢?
public class Test {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
}
}
如果我们是这样创建Bean的话,就不存在循环依赖引发的问题了。
Spring解决循环依赖也正是这样的思路,在属性赋值的时候把不完整的Bean注入进去,待后续流程走完,不就得到一个完整的单例Bean了么,具体流程如下:
要想满足上述流程,那Spring容器就不能只用一个一级缓存了,还得搞一个Map,存半成品的Bean,于是乎有了这两个缓存
// 一级缓存 private final MapsingletonObjects = new ConcurrentHashMap<>(256); // 二级缓存 private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);
按照我们的分析,加一个二级缓存不就能够解决循环依赖的问题了么,为什么Spring要再搞一个三级缓存?吃饱撑着?
当然不是。你想想看,Spring有一个重要特性就是Aop,这个Aop有整合AspectJ的Aop,还有事务的Aop,可能还有其他的Aop。如果两个Bean存在循环依赖同时,又需要被创建成代理对象,那双方依赖的对象就应该是代理对象,而不是实例化生成的原对象了。要实现这样的目的,就需要在实例化后把半成品的代理对象生成出来,以供依赖对象去引用。
所以啊,Bean实例化后要做的事情还不少,那能不能以一种更为优雅、更具扩展性、更解耦的方式来实现呢?
那就用三级缓存吧!获取Bean的时候先从一级缓存拿,然后判断是不是有循环引用,有的话再从二级缓存拿,二级缓存没有,就从三级缓存拿,三级缓存拿出来的Bean再放回二级缓存里。
三级缓存value放的是一个函数接口,这样一来,你要处理什么Aop或者处理一些其他需求,都在这个函数接口里处理吧,反正二级缓存就负责存放半成品Bean,其他不管。
看下三级缓存value的接口
@FunctionalInterface public interface ObjectFactory{ T getObject() throws BeansException; }
三级缓存的value是一个函数接口,Bean实例化后会往三级缓存放入如下的函数:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
//上述条件满足,允许早期暴露对象
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//把我们的早期对象包装成一个singletonFactory对象 该对象提供了一个getObject方法,
// 该方法内部调用getEarlyBeanReference方法,该方法再去调后置处理器
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
getEarlyBeanReference是个啥,跟进去看看
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
这里竟然是调用后置处理器!真是巧夺天工啊,这里后置处理器一般情况下就只有AutowiredAnnotationBeanPostProcessor,他返回的就是传入的Bean(即实例化后的半成品Bean)----->解决了循环依赖
如果还开启了Aop,那会多一个后置处理器,他会去生成代理对象----->解决了循环依赖下Aop问题
Spring在创建Bean的时候考虑到了有可能会并发获取Bean,所以加了两把锁,如下
所以Sping使用了一、二、三级缓存解决了循环依赖问题,也解决了循环依赖下Aop的问题,真是女少口阿!!!
但是要注意的是,Spring只是帮我们解决了属性注入和sertter注入的循环依赖问题,至于构造器注入的方式还有像原型作用域的Bean是不会处理循环依赖的,还需要自行解决,比如加个@Lazy注解。