Java框架 —— Spring(循环依赖问题)

打印 上一主题 下一主题

主题 900|帖子 900|积分 2700

循环依赖

 简朴来说,就是多个bean之间相互依赖,终极形成闭环,极限情况,以致可能出现本身依赖本身
  1. @Component
  2. public class A {
  3.     // A中注入了B
  4.         @Autowired
  5.         private B b;
  6. }
  7. @Component
  8. public class B {
  9.     // B中也注入了A
  10.         @Autowired
  11.         private A a;
  12. }
复制代码

循环依赖发生的场景


  • 通过构造器注入循环依赖(无法解决)
当Bean通过构造器注入相互依赖时,Spring无法解决这种循环依赖,因为在构造器调用之前,Bean尚未创建,无法注入。
  1. @Component
  2. public class Person {
  3.     public Person(User user) {}
  4. }
  5. @Component
  6. public class User {
  7.     public User(Person person){}
  8. }
复制代码
项目中如果出现此类代码,会抛出非常BeanCurrentlyInCreationException
  1. Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
  2.         at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
  3.         at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
  4.         at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
  5.         at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
复制代码

  • 通过属性注入循环依赖(可以解决)
通过字段(field)或setter方法注入依赖时,Spring可以通过三级缓存来解决循环依赖问题。
  1. @Component
  2. public class User {
  3.     @Autowired
  4.     Person person;
  5. }
  6. @Component
  7. public class Person {
  8.     @Autowired
  9.     User user;
  10. }
复制代码
项目可以正常运行

  • 多例Bean的属性注入循环依赖(无法解决)
对于多例(prototype)作用域的Bean,Spring默认不会在启动时初始化,而是在使用时才初始化,因此可能不会立即出现循环依赖问题,但在现实使用中可能会遇到。
  1. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  2. @Component
  3. public class User {
  4.     @Autowired
  5.     Person person;
  6. }
  7. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  8. @Component
  9. public class Person {
  10.     @Autowired
  11.     User user;
  12. }
复制代码
项目启动时,不会立即抛非常,因为多例的只有在被调用时,才会进行初始化,当被调用时就会抛非常
  1. org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
  2.         at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
  3.         at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
  4.         at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
复制代码
Spring容器的三级缓存


  • 一级缓存:存放完全初始化,且属性赋值完毕的Bean,可以直接使用
  • 二级缓存:存放早期Bean的引用,即已实例化但是未装配属性的Bean
  • 三级缓存:存放Bean工厂,即实例化Bean的工厂对象,用于解决循环依赖中Bean属性未完全装配的问题
解决循环依赖的步调

 Spring在实例化Bean时,会先创建一个空的Bean对象,并将其放入三级缓存中,Spring开始对Bean进行属性赋值,如果发现循环依赖,会通过工厂方法提前暴露一个原始的Bean实例,并将其放入二级缓存,这样,当其他Bean引用该Bean时,可以使用这个原始实例,避免了循环依赖
简朴的循环依赖(没有AOP)


结合了AOP的循环依赖


对A进行了AOP代理的话,此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象

三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?
答:这个工厂的目的在于耽误对实例化阶段天生的对象的代理,只有真正发生循环依赖的时间,才去提前天生代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

总结


Spring 如何解决循环依赖
 Spring通过三级缓存去解决,一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象(earlySingletonObjects),三级缓存为早期曝光对象工厂(singletonFactories)。
 当A、B两个类发生循环依赖时,在A完成实例化后,就使用实例化后的对象创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的是A代理后的对象;如果A没有被AOP代理,那么这个工厂获取到的就是A的实例化对象。
 当A进行属性注入时,会去创建B,同时B又依赖于A,所以创建B的同时又会调用getBean(a)来获取需要的依赖,此时getBean(a)会从缓存中获取:第一步,先获取三级缓存中的工厂;第二步,调用对象工厂的getObject方法来获取对应对象,得到这个对象后,将其注入到B中,然后B走完它的生命周期,当B创建完后,会将B再注入到A中,此时,A在完成它的生命周期。至此,循环依赖结束


为什么要使用三级缓存呢?二级缓存能解决循环依赖吗
 如果要使用二级缓存解决循环依赖,意味着全部Bean在实例化后就要完成AOP代理,这样违背了Spring计划的原则,Spring在计划之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处置惩罚器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。




参考文章:口试必杀技,讲一讲Spring中的循环依赖

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表