什么是循环依赖?
就是现在有两个类 M N,其中在M中注入了N,N中注入了M,这就是循环依赖。
其实在java原本的代码逻辑上,循环依赖是没有问题的,注入的时候new一个新的不就行了吗。、所以循环依赖只在spring会有问题,因为spring中有bean生命周期的流程,在属性注入的时候如果M类依赖了N,那么就要去单例池中取N。但是N现在还没有实例化,所以在N的生命周期中,在属性注入的时候发现N也依赖了M。这时候你等我实例化,我等你实例化,这不就死锁了吗。
样例代码如下:
@Component
public class M {
@Autowired
N n;
public void print(){
System.out.println("print-M");
}
}
@Component
public class N {
@Autowired
M m;
public void print(){
System.out.println("print-N");
}
}
@Test
public void defaultCycle(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
context.getBean(M.class).print();
context.getBean(N.class).print();
}
//打印结果
print-M
print-N
代码分析
循环依赖的代码在这个位置
AnnotationConfigApplicationContext.refresh() -> finishBeanFactoryInitialization() -> beanFactory.preInstantiateSingletons() -> getBean()
按照我们刚才的代码,这里是先拿到M类来进行实例化,也就是说getBean(m)
doGetBean() -> getSingleton()
这个方法判断M是否在单例池中
回到doGetBean()方法继续往下看,终于有一天到了这个方法
这里是个lambda表达式,我们先看getSingleton()方法
beforeSingletonCreation()
就是在这里,将M放入了正在创建集合
然后回到getSingleton()方法继续往下,有个singletonFactory.getObject()方法,因为进入这个方法的时候有个lambdm表达式,所以这个方法其实调用的就是createBean()
createBean() -> doCreateBean()方法中,把M给new出来了
这个方法继续往下走,走到这个地方就是循环依赖的主要逻辑之一
判断了一下是否需要循环依赖,操作了几个集合
这几个集合就是三级缓存的内容,后面我们再分析
然后代码继续往下走,进入这个方法中,这个方法就比较复杂了。
可以理解为,这里就是要注入依赖的N,然后发现N不在单例池中,于是就要去实例化N
然后N也被放入到创建集合中
然后下面的逻辑也是一样的,N同样被放到那几个集合中
然后N也操作了这个方法进行属性填充,这时候发现需要注入M
那就也调用doGetBean来获取M
这次再进行判断,M已经在被创建集合中了,所以这里会返回值。
那么重点来了,这里就是所谓的三级缓存,其实并没有spring官方文档说哪个是一级哪个是二级、三级,就是看我们自己的理解。
而我在这里就把
单例池Map
Map
Map
还记得上面的判断是否需要开启循环依赖处理的逻辑中 addSingletonFactory()方法吗?
那里操作了三个集合,在这里就用到了。
singletonObjects中找不到M,earlySingletonObjects二级缓存也找不到,singletonFactories这个里面可以找到,并且是m和ObjectFactory工厂。
这里有几个问题,一个是三级缓存为什么一定要放一个工厂,而不是直接放半成品的bean呢?循环依赖的时候,N的属性注入M,为什么不放一个半成品的M,最后M实例化完成不就可以了吗。为什么非要换成工厂对象放进去。
另一个是,二级缓存不就可以存储半成品的bean了吗,为什么一定要搞一个三级缓存?
第三个问题:为什么不把所有的类都提前进行AOP代理,全都放进二级缓存中,那不就不需要三级缓存了吗?
解答:
一、为什么不放一个半成品的bean,因为在N的实例化链路中,有可能在回调方法中直接使用m这个类的方法,这时候的半成品就有问题了。
二、singletonFactories三级缓存中存储的是个m和工厂,因为不能在N的属性中注入半成品的m,所以在这里要通过这个三级缓存把它先实例化出来,注入N的属性中。
earlySingletonObjects二级缓存是存储这个M,当三级缓存通过工厂方法实例化出来之后,就会放入这个二级缓存,那么如果有三个类循环依赖的话,就不用重复实例化M了。并且在M后续的链路中,如果有AOP代理这种扩展的话,在上面已经执行了,在后续就不会再执行aop扩展了。
三、首先我们来看aop在正常情况下是什么时候完成的,是在bean创建完全之后通过annotationawareaspectjautoproxycreator这个后置处理器来完成的。在这个后置处理postprocessafterinitialization()方法中对初始化后的bean进行aop代理。
spring设计的原则就是先初始化bean,最后进行代理对象的创建。设计之初就是然bean在生命周期的最后一步来完成代理。而循环依赖严格来说并不是一个正常的流程,那么如果为了解决循环依赖而把所有bean的代理动作放在前面,就违背了spring的设计原则。
那么三级缓存的目的就为了在没有循环依赖的情况下,让bean的代理对象在最后一步进行创建,使得bean符合spring的生命周期原则。
面试回答三级缓存:
大概说一下bean的生命周期,把需要三级缓存的场景说一下,然后再解释循环依赖



