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

Spring解决循环依赖的关键spring

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

什么是三级缓存

三级缓存的代码以下:
image.png缓存

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

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

**注意只有一级缓存用了ConcurrentHashMap, 第二级、第三级都是使用了普通的hashmap,由于除了一级缓存,查找、新增和删除二三级缓存节点的关键代码使用同步代码块实现
image-20210330212735999.png
事实上若是只使用ConcurrentHashMap,不使用同步代码块反而会有问题,由于ConcurrentHashMap只能保证对节点单个操做的原子性、可见性,好比想上面先判断没有再新增就无法完成,get方法也不是一个绝对正确的结果。函数

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

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

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

  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就能够完成建立了

这样看好像只须要第一级缓存和第三级缓存就能够解决循环依赖的问题了spa

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

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

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

咱们先思考一下若是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时会一二三级缓存依次判断,最后能在二级缓存中直接获得代理对象,就不会去三级缓存中获取工厂构建一个了code

关于代理bean执行BeanPostProcessor的细节

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

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

spring是在执行提早代理加强时把原始对象又放到了一个专门用来作判断是否已经进行了aop的map(earlyProxyReferences)里面,lambda加强代码以下:
image-20210331220423404.png
在后置处理器执行aop的方法,先去这个map里面去找这个对象,要是发现这个map里面有这个bean了就再也不执行aop了
image-20210331220607331.png

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

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

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

解决办法:

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