Bean的生命周期参考:https://blog.csdn.net/sumengnan/article/details/113702527
1、三级缓存是什么?
- 一级缓存(单例池):singletionObjects ConcurrentHashMap<beanName,bean对象>
- 二级缓存(早期单例对象):earlySingletionObjects HashMap<beanName,不完整bean对象>(主要用于解决循环依赖问题)
- 三级缓存(单例的工厂):singletonFactories HashMap<beanName,ObjectFactory(lambda表达式)> (当出现了循环依赖,并且需要进行aop时才使用,相当于做一个准备工作)
顺便提一下为什么一级缓存用ConcurrentHashMap,而二三级缓存却使用HashMap?
因为一级缓存保存的是单例bean,必须要保证线程安全的。
而二三级缓存是一对的,所有用到它们的地方已经加了synchronized锁保证了线程安全,所以为了保证性可以直接使用HashMap。如图:
再问一下单例bean和单例模式有什么区别?
- 单例模式:表示对象只能有一个。
- 单例bean:只要bean name不相同,即是对象相同也没关系。
2、Bean的生命周期简述
正常的bean生命周期为:
- 实例化得到bean对象。
- 填充属性。
- 进行其他操作(简略说明)
- 放入单例池(一级缓存)
举例:
例如:aService类依赖bService类,bService依赖aService
@Component("aService")
public class AService {
@Autowired
private BService bService;
public BService getBService() {
System.out.println(bService);
return bService;
}
}
=====================
@Component("bService")
public class BService {
@Autowired
private AService aService;
public AService getAService() {
System.out.println(aService);
return aService;
}
}
无论先初始化aService还是bService结果都一样。那就先处理aService
此时aService的生命周期为:
1、实例化得到aService对象(spring底层是通过反射获取的)。
2、填充bService属性 --> 从单例池(一级缓存)查询 --> 找不到则实例化bService
下面开始bService的生命周期:
2.1、实例化bService对象。
2.2、填充aService对象 --> 从单例池(一级缓存)查询 --> 找不到则实例化aService。
2.3、又去实例化aService对象 -->一直重复 (循环依赖)
上面就叫做spring出现了循环依赖问题。
如何解决?
增加一个中间人(缓存)。这里就是二级缓存。
此时aService的生命周期就变成了如下:
1、实例化得到aService对象 --> 放入二级缓存Map<aService,aService不完整对象>。
2、填充bService属性 --> 从单例池(一级缓存)查询 --> 找不到则去二级缓存查询 --> 找不到则实例化bService
下面开始bService的生命周期:
2.1、实例化bService对象 --> 放入二级缓存Map<bService,bService不完整对象>。
2.2、填充aService对象 --> 从单例池(一级缓存)查询 --> 找不到去二级缓存查询 --> 找到了 。
2.3、进行其他操作
2.4、把bService对象放入单例池
3、进行其他操作
4、把aService对象放入单例池
此时循环依赖问题就已经解决。
注意:上面第2.2步我们把aService的不完整对象赋值给了bService的属性,上面步骤看似有个“问题”,实则没有问题。
为什么spring还需要三级缓存,不要不行吗?
当bean出现循环依赖并且需要进行aop的时候,二级缓存就不行了。
例如:aService正常进行aop操作的生命周期:
- 实例化得到bean对象。
- 填充属性。
- 进行其他操作(包括aop)
- 放入单例池(一级缓存)
可以看到正常的aop操作是在第三步进行。但是bService需要aService属性,所以出现了循环依赖,就需要提前进行aop,放到第二步进行。
如何判断bean出现了循环依赖问题?
我们可以增加一个Set集合,用来保存正在创建中的bean,在第二步时判断,如果bService所依赖的aService正在创建中,则出现了循环依赖问题,如果需要则提前进行aop。
最终完整解决循环依赖aService的生命周期为:
0、singletonsCurrentlyInCreation Set集合<aService>(代表创建中的bean)
1、实例化得到aService不完整对象 -->放入三级缓存<aService,aService原始对象、beanName、beanDefinition>(spring底层使用的lambda表达式编程,通过这三个信息判断是否要进行aop)
2、填充bService属性 --> 从单例池(一级缓存)查询 --> 找不到则去二级缓存查询 --> 找不到则实例化bService
下面开始bService的生命周期:
2.0、singletonsCurrentlyInCreation Set集合<bService>
2.1、实例化得到bService不完整对象。
2.2、填充aService对象 --> 从单例池(一级缓存)查询 --> 找不到则去二级缓存查询 --> 发现aService正在创建中 --> 出现了循环依赖 --> 通过第三级缓存取到lambda表达式并执行获取原始对象 --> 如果需要则进行aop得到代理对象 -->放入二级缓存。
如图:
后置处理器InfrastructureAdvisorAutoProxyCreator实现类是用于进行aop代理的。在它的父类AbstractAutoProxyCreator中有如下两个方法提前进行aop和正常进行aop:
通过earlyProxyReferences这个Map来判断是否提前进行了aop,如果进行过,则正常步骤就不进行aop了。
扩展问题:
为什么lambda表达式只执行一次,之后就remove了,结果放入了二级缓存?
为了保证单例。防止lambda多次aop操作。
2.3、进行其他操作
2.4、把bService对象放入单例池
2.5、singletonsCurrentlyInCreationSet.remove(bService)
3、进行其他操作
4、把aService对象放入单例池
5、singletonsCurrentlyInCreationSet.remove(aService)
3、@Lazy注解为什么可以解决循环依赖?
原理:
1. A的创建: A a=new A();
2. 属性注入:发现需要B,查询字段b的所有注解,发现有@lazy注解,那么就不直接创建B了,而是使用动态代理创建一个代理类B
3. 此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A
4、spring是如何防止在多线程环境下读取到不完整的bean?
加了两把锁。
1、一把锁加在了整个bean的创建过程。
2、另一把加在了从缓存中取数据
因为一开始getBean时要先查缓存,如果没有才创建bean