Spring循环依赖学习笔记

Spring循环依赖学习笔记

出问题的代码片段

@Service
public class TestServiceA {

    @Autowired
    private TestServiceB testService2;

    @Async
    public void testA() {
    }
}

@Service
public class TestServiceB {

    @Autowired
    private TestServiceA testService1;

    public void testB() {
    }
}

这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制,让我们根本无法感知它有问题,因为spring默默帮我们解决了。

spring内部有三级缓存:

  • singletonObjects 一级缓存,用于保存实例化、属性注入、初始化完成的bean实例
  • earlySingletonObjects 二级缓存,用于保存实例化完成,且未属性注入的bean实例
  • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

spring是如何解决循环依赖的图示:

所谓创建Bean实际上就是调用getBean() 方法,这个方法可以在AbstractBeanFactory这个类里面找到,getBean是从容器中获取bean的入口方法,它里面又调用了doGetBean方法

doGetBean
protected  T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
   
  // 尝试通过bean名称获取目标bean对象,比如这里的A对象
  Object sharedInstance = getSingleton(beanName);
  // 我们这里的目标对象都是单例的
  if (mbd.isSingleton()) {
     
    // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
    // 目标对象
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // 尝试创建目标对象
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}

这个方法里面有两个名称为getSingleton的方法,第一个getSingleton是从缓存中查找bean,如果缓存未命中,则走第二个getSingleton,尝试创建目标对象并注入依赖。
当第一次调用doGetBean获取A对象,第一个getSingleton返回空,进入第二个getSingleton创建A对象,注入B对象。调用doGetBean获取B,第一个getSingleton返回空,进入第二个getSingleton创建B对象,获取并注入原始A对象,此时B对象初始化完成。最后将B对象注入A中,A完成初始化。
先看第一个getSingleton的源码

getSingleton(String beanName, boolean allowEarlyReference)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   
  // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回
  Object singletonObject = this.singletonObjects.get(beanName);
   
  // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象
  // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
     
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         
        // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的
        // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品
        ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
           
          // 获取目标对象的实例
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

首先从一级缓存singletonObjects获取目标对象,若不存在且目标对象被标记为创建中,从二级缓存earlySingletonObjects中获取bean。如果不存在,继续访问三级缓存singletonFactories,得到bean工厂对象,通过工厂对象获取目标对象。将目标对象放入二级缓存,删除三级缓存。
第二个getSingleton方法

getSingleton(String beanName, ObjectFactory<?> singletonFactory)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
 
        // ......
         
        // 调用 getObject 方法创建 bean 实例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;
 
        if (newSingleton) {
            // 添加 bean 到 singletonObjects 缓存中,并从其他集合中将 bean 相关记录移除
            addSingleton(beanName, singletonObject);
        }
 
        // ......
         
        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

该方法的主要逻辑是调用singletonFactory的getObject()方法创建目标对象,然后将bean放入缓存中。
接下来看一下getObject方法的实现,由于在doGetBean中调用getSingleton时第二个参数是用匿名内部类的方式传参,实际上调用的是createBean方法,而createBean又调用了doCreateBean。

doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
        throws BeanCreationException {
 
    BeanWrapper instanceWrapper = null;
 
    // ......
 
    // 创建 bean 对象,并将 bean 对象包裹在 BeanWrapper 对象中返回
    instanceWrapper = createBeanInstance(beanName, mbd, args);
     
    // 从 BeanWrapper 对象中获取 bean 对象,这里的 bean 指向的是一个原始的对象
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
 
    /*
     * earlySingletonExposure 用于表示是否"提前暴露"原始对象的引用,用于解决循环依赖。
     * 对于单例 bean,该变量一般为 true。更详细的解释可以参考我之前的文章
     */
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 添加 bean 工厂对象到 singletonFactories 缓存中
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                /*
                 * 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP
                 * 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回
                 * bean,所以大家可以把
                 *      return getEarlyBeanReference(beanName, mbd, bean)
                 * 等价于:
                 *      return bean;
                 */
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }
 
    Object exposedObject = bean;
 
    // ......
     
    // 填充属性,解析依赖
    populateBean(beanName, mbd, instanceWrapper);
 
    // ......
 
    // 返回 bean 实例
    return exposedObject;
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 将 singletonFactory 添加到 singletonFactories 缓存中
            this.singletonFactories.put(beanName, singletonFactory);
 
            // 从其他缓存中移除相关记录,即使没有
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

方法的主要逻辑如下

  1. 用createBeanInstance创建目标对象
  2. 将对象添加到singletonFactories缓存中
  3. populateBean注入依赖
Spring在实例化bean时,会先创建当前bean对象,放入缓存中,然后以递归的方式获取所依赖的属性。当注入属性时,如果出现了循环依赖则会从缓存中获取依赖对象。

Spring框架为什么采用三级缓存而不采用二级缓存来解决循环依赖?

是因为CGLIB动态代理的存在!假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行singleFactory.getObject()方法都会生成一个新的代理对象,这是不行的,因为TestServiceB是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。
还有一个注意的点,既然singleFactory.getObject()返回的是代理对象,那么注入的也应该是代理对象,我们可以看到注入的确实是经过CGLIB代理的AService对象。所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法,都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。

缓存解决循环依赖的思路有两种:

第一种:不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存。出现循环依赖时,其它对象直接就可以取到代理对象并注入,两个级别的缓存就够了。

第二种:不提前创建好代理对象,在出现循环依赖被其它对象注入时,才实时生成代理对象。

Spring选择了后一种,如果使用两个缓存解决循环依赖,意味着Bean在构造完成后就创建代理对象,这样就违背了Spring的设计原则。Spring结合AOP和Bean的生命周期,是在Bean创建完成之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理器的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。使用第一种方案如果出现了循环依赖,那没有办法,只有先给Bean先创建代理,但是在Spring设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化之后就立马完成代理。

参考资料

  • https://www.cnblogs.com/tiancai/p/16092145.html
  • https://www.zhihu.com/question/438247718/answer/1730527725
  • https://juejin.cn/post/6844904154163314702
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值