【03】spring经典面试题:说一下循环依赖以及解决方法

什么是循环依赖

很简单就是一个对象依赖另外一个对象,比如:

class A{
   // A依赖B
   public B b;
}
class B{
   //B依赖A
   public A a;
}
A a = new A();
B b = new B();
a.b = b;
b.a = a;

这样A和B互相依赖,但是看起来也没什么问题,在spring中循环依赖就是个问题了?因为在spring中,bean并不是简单的new出来的,而是经历了一系列的生命周期,就是在生命周期里出现了循环依赖问题。当然有的循环依赖问题spring已经帮我们解决好了,有的需要我们自己解决。

bean的生命周期里循环依赖

bean的生命周期
bean的生命周期

得到一个完整的bean是需要填充属性(依赖注入)和初始化后(aop)的,循环依赖是发生在填充属性,那么具体过程是怎样的?

那刚才的例子来说,当A生成了一个原始对象之后,就会去给b填充属性赋值,此时会根据b的类型和名字去BeanFactory中获取B

类的单例bean。如果BeanFactory中存在b,那么直接赋值给A的原始对象属性b,如果不存在则需要生成一个B对应的bean,然后赋值给b。

循环依赖就发生在第二种情况,也就是不存在B类对应的bean,需要去生成B类对应的bean,此时B类对应的bean需要经过完整的bean生命周期。那么在创建B类对应bean的时候,会发现需要A类对应的bean,此时A类对应的bean正在创建过程中,就导致了循环依赖。

ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中)

你也许会说直接拿正在创建的A不就行了?

不行,因为A还没有走完生命周期,并且在初始化后会经过动态代理,也就是现在有可能现在创建A和初始化后经过动态代理后的A不是同一个。也就是如果现在赋值给B类的a属性有可能不是最终A类生成的bean对象,如果真的想要解决循环依赖那么就需要提前走动态代理让B类提前拿到初始化后的经过动态代理的A类bean。在Spring中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是「三级缓存」Java面试技术栈

解决循环依赖

三级缓存

在Spring框架中,三级缓存是指在创建Bean实例的过程中,Spring容器使用的三个缓存区域,它们分别是singletonObjects、earlySingletonObjects和singletonFactories。每个缓存区域都有不同的作用和功能,用于支持对象的创建、依赖注入和循环依赖解析。

  • 一级缓存:singletonObjects,保存已经完全初始化的单例Bean对象。也就是经过完整生命周期的bean。
  • 二级缓存:earlySingletonObjects,保存已经创建但尚未完全初始化的单例Bean对象。
  • 三级缓存:singletonFactories,当需要解决循环依赖时,会将尚未初始化的Bean对象的工厂实例放入该缓存区域。这样可以在循环依赖的情况下,提供一个用于创建Bean对象的临时实例。

深入理解一下这三个缓存的关系

在属性注入的时候需要从缓存中拿值,具体源码如下

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   //首先从一级缓存中取值
   Object singletonObject = this.singletonObjects.get(beanName);
    //如果一级缓存没有,并且该 beanName 对应的 bean 是正在创建过程中
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       //从二级缓存中拿
      singletonObject = this.earlySingletonObjects.get(beanName);
       //如果二级缓存没有,并且允许循环依赖(默认是允许的)
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            // 这里再次判断下,双重检查
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                   //直接看到这里,ObjectFactory是个接口
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                      //调用接口的方法,并且把返回的值放入到二级缓存中,并且将beanName从三级缓存中删除
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

一会再总结,先看什么时候会给三级缓存存值。

  // 为了解决循环依赖提前缓存单例创建工厂
  // Eagerly cache singletons to be able to resolve circular references
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
   if (logger.isTraceEnabled()) {
    logger.trace("Eagerly caching bean '" + beanName +
      "' to allow for resolving potential circular references");
   }
   // 循环依赖-添加到三级缓存
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  // Initialize the bean instance.
  Object exposedObject = bean;
  try {
   // 属性填充
   populateBean(beanName, mbd, instanceWrapper);

   // 初始化
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }

通过源码可以发现,给三级缓存赋值的地方是再填充属性之前,也就是所有的bean在生命周期在实例化后,属性赋值前给三级缓存赋值,前提是spring允许循环依赖(默认是允许的)。

添加三级缓存
添加三级缓存

给三级缓存赋值的ObjectFactory接口实现:

/**
  * Obtain a reference for early access to the specified bean,
  * typically for the purpose of resolving a circular reference.
  * 获取对指定bean的早期访问的引用,通常是为了解析循环引用。
  */

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
          //提前进行AOP
         exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
      }
   }
   return exposedObject;
}

好了,现在问题解决了。最大的问题就是放入缓存的没走完生命周期的bean和走完生命周期(也就是最后初始化后经过动态代理)的bean不是同一个。现在是当我们出现循环依赖了,我们先去让bean进行动态代理,拿着动态代理之后的bean去进行依赖注入,并且还可以继续A原始Bean的生命周期,因为代理后的bean内部有一个变量target,target指向A原始的bean,所以A原始bean继续走之后的生命周期不会影响到代理后的bean。

代理
代理

其实这里边还有一个缓存就是earlyProxyReferences,在我们提前进行AOP的时候,会给这个缓存赋值。也就是说A在生命周期的最后进行AOP的时候会判断这个缓存里边有没有,如果有就跳过了。

总结一下:

还是用A,B类去举例说明,我们把没经过完生命周期的bean叫做原始bean,把经过AOP的bean叫做代理bean。

「先总结三级缓存中取值」

  1. 首先从一级缓存中取值,如果存在直接返回。
  2. 如果一级缓存中没有,并且这个bean是正在创建的,就从二级缓存中拿,如果二级缓存中有直接返回。
  3. 如果二级缓存中没有,并且spring支持循环依赖。
  4. 双重检查锁,重新走1,2步骤
  5. 从三级缓存中拿,三级缓存中存放的是ObjectFactory就是一个接口。
  6. 执行ObjectFactory也就是让这个bean先执行AOP,然后把返回值,也就是代理bean存放到二级缓存中,并且把三级缓存中的值删除。

「spring解决循环依赖」

  1. 创建A原始bean
  2. 经过生命周期,实例化后,属性赋值前,会给三级缓存添加一个ObjectFactory的方法(方法内容是提前进行代理)
  3. 进行属性注入时候发现B,B没有在缓存中。
  4. A中止创建,开始创建B的原始bean。
  5. B经过生命周期,实例化后,属性赋值前,会给三级缓存添加一个ObjectFactory的方法。
  6. 属性注入,发现需要注入A。
  7. 从三级缓存中拿值,并且赋值给B的属性,然后B走完生命周期。
  8. 将走完生命周期的B存放到singletonObjects中。
  9. A原始bean继续走生命周期。
  10. 当A原始bean生命周期到初始化后,也就是AOP那步骤,会判断缓存 earlyProxyReferences有没有提前AOP,如果有,就无需再进行AOP。
  11. 要从二级缓存 earlySingletonObjects中得到代理对象A,然后入singletonObjects中。 Java面试技术栈
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值