【spring源码分析】spring bean的循环依赖问题

Bean的生命周期参考:https://blog.csdn.net/sumengnan/article/details/113702527

1、三级缓存是什么?

  1. 一级缓存(单例池):singletionObjects     ConcurrentHashMap<beanName,bean对象>
  2. 二级缓存(早期单例对象):earlySingletionObjects     HashMap<beanName,不完整bean对象>(主要用于解决循环依赖问题)
  3. 三级缓存(单例的工厂):singletonFactories  HashMap<beanName,ObjectFactory(lambda表达式)> (当出现了循环依赖,并且需要进行aop时才使用,相当于做一个准备工作)

顺便提一下为什么一级缓存用ConcurrentHashMap,而二三级缓存却使用HashMap?

因为一级缓存保存的是单例bean,必须要保证线程安全的。

而二三级缓存是一对的,所有用到它们的地方已经加了synchronized锁保证了线程安全,所以为了保证性可以直接使用HashMap。如图:

再问一下单例bean和单例模式有什么区别?

  1. 单例模式:表示对象只能有一个。
  2. 单例bean:只要bean name不相同,即是对象相同也没关系。

2、Bean的生命周期简述

正常的bean生命周期为:

  1. 实例化得到bean对象。
  2. 填充属性。
  3. 进行其他操作(简略说明)
  4. 放入单例池(一级缓存)

举例:

例如: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操作的生命周期:

  1. 实例化得到bean对象。
  2. 填充属性。
  3. 进行其他操作(包括aop)
  4. 放入单例池(一级缓存)

可以看到正常的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

猜你喜欢

转载自blog.csdn.net/sumengnan/article/details/113778927
今日推荐