Spring解决循环依赖相关问题分析

Spring解决循环依赖的关键

  1. 三级缓存
  2. 提前曝光

什么是三级缓存

三级缓存的代码如下:
Spring解决循环依赖相关问题分析_第1张图片

  • 第一级缓存 singletonObjects(就是我们通常所说的单例池)
  • 第二级缓存 earlySingletonObjects
  • 第三级缓存 singletonFactories(存储的是创建bean的工厂,工厂在源码中就是lambda表达式,它持有一个原始对象,要是需要发现这个bean需要被代理,执行这个lambda则会返回这个bean的代理对象)

bean中定义了切面、有@Transactional或@Async这类的增强注解,则必须在元素对象的基础上加上增加逻辑,生成代理类

**注意只有一级缓存用了ConcurrentHashMap, 第二级、第三级都是使用了普通的hashmap,因为除了一级缓存,查找、新增和删除二三级缓存节点的关键代码使用同步代码块实现
Spring解决循环依赖相关问题分析_第2张图片
事实上如果只使用ConcurrentHashMap,不使用同步代码块反而会有问题,因为ConcurrentHashMap只能保证对节点单个操作的原子性、可见性,比如想上面先判断没有再新增就没法完成,get方法也不是一个绝对正确的结果。

bean一个大概的创建流程:
  1. 找到需要管理的bean,根据class生成BeanDefinition
  2. new出一个原始对象
  3. 填充依赖的属性
  4. 调用BeanPostProcessor
  5. 把这个bean添加到单例池(一级缓存)

思考:只使用两级缓存如何解决循环依赖

那么AService和BService完成初始化的过程是什么样的呢?

  1. ASerive生成BeanDefinition,new出一个原始对象,把这个原始对象传递给构造Lambda持有,把这个Lambda放到三级缓存中
  2. AService开始填充属性,发现需要填充BService,从一三级缓存依次查找,发现都没有BServie,调用BService的创建
  3. BSerive生成BeanDefinition,new出一个原始对象,把这个原始对象传递给构造Lambda持有,把这个Lambda放到三级缓存中
  4. 开始属性填充,发现需要依赖ASerive,一级缓存中找不到,于是从三级缓存中找生成bean的工厂,发现能找到,于是调用这个工厂Lambda表达式,返回AService并填充到属性中,BService完成属性注入,走完创建流程,添加到单例池中
  5. Bservice的创建函数返回,AService接着进行属性填充,从单例池中找到BService放到自己属性中,ASerive就可以完成创建了

这样看好像只需要第一级缓存和第三级缓存就可以解决循环依赖的问题了

Spring为什么使用到了三级缓存缓存呢?

其实使用三级缓存主要是在需要被代理的bean出现循环依赖的时候起作用

需要被代理时只使用一级和三级缓存存在的问题

我们先思考一下如果AService上定义了切面,这时候三级缓存中的构造Lambda返回的就不是原始对象了,而是被代理后的对象,这时候上面的流程中第4步中调用Lambda返回的就是AService的一个代理对象
假设AService同样依赖CService,而CService也需要依赖AService,这样AService填充进BService后,会接着填充CService, 使得CService也开始进行创建,CService的创建时需要注入AService,同样能从三级缓存中找到工厂,调用工厂同样返回AService的一个代理对象,这样BService和CService创建过程都生成了一个AService的代理对象,而且持有的都不是同一个AService!

如何利用第二级缓存解决代理bean的循环依赖

其实也比较简单,上面的步骤中第4步BService从三级缓存中获取到工厂生产代理对象时,把这个对象放到第二级缓存中,这样CService需要注入AService时会一二三级缓存依次判断,最后能在二级缓存中直接得到代理对象,就不会去三级缓存中获取工厂构建一个了

关于代理bean执行BeanPostProcessor的细节

其实生成代理对象一般还是在执行BeanPostProcessor的这一步执行的,在这一步会根据原始对象生成代理对象放到单例池中
只用出现了循环依赖的时候才会调用三级缓存中的工厂提前进行代理,上面的流程中第四步,BService填充AService时发现BSerice不在单例池中时候,就可以评定一定是出现循环依赖了,接着在二级缓存中也找不到,于是开始找三级缓存中的工厂进行提前代理
但是要是之后BeanPostProcessor有生成一个代理对象放到单例池中,不是又出现了两个代理对象了吗?

BeanPostProcessor如何判断已经提前进行了代理,不再生成代理对象

spring是在执行提前代理增强时把原始对象又放到了一个专门用来做判断是否已经进行了aop的map(earlyProxyReferences)里面,lambda增强代码如下:
Spring解决循环依赖相关问题分析_第3张图片
在后置处理器执行aop的方法,先去这个map里面去找这个对象,要是发现这个map里面有这个bean了就不再执行aop了
Spring解决循环依赖相关问题分析_第4张图片

@Async注解引发的Spring循环依赖分析

@Aysnc 注解的Bean的创建代理的时机特别特殊:
@EnableAsync开启时会向容器注入AsyncAnnotationBeanPostProcessor,它是一个BeanPostProcessor,实现了postProcessAfterInitialization,具体的实现在其父类AbstractAdvisingBeanPostProcessor中,在设个PostProcessor会生成一个代理,就及完成了提前代理也会生成一个新的代理

一个类有同时有循环依赖提前代理 @Async生成代理 的情况时,会生成两个不同的代理对象,一个是异步的代理对象,就会导致代码在AbstractAutowireCapableBeanFactory.doCreateBean方法中直接抛出错误(最后一步会判断二级缓存中的对象和Processor执行后的对象不一致时抛出异常)

解决办法:

  1. 或者在依赖注入的地方,加上@lazy注解
    @Autowired 注入时配合@lazy是怎么起作用的跟着源码看了一下,核心意思就是在Spring初始化ioc容器时,使用@Lazy的属性可以不加载,这样启动不报错,运行时再加载)
  2. 将用@Async注解 的方法都移出去放到另一个类中

你可能感兴趣的:(javaspring)