Spring源码分析之循环依赖及解决方案(二)

简介: Spring源码分析之循环依赖及解决方案

3. 代理对象与代理对象

代理对象与代理对象的循环依赖是怎么样的呢?解决过程又是如何呢?这里就留给小伙伴自己思考了,其实和普通Bean与代理对象是一模一样的,小伙伴想想是不是呢,这里我就不做分析了。

4. 普通Bean与工厂Bean

这里所说的普通Bean与工厂Bean并非指bean与FactoryBean,这将毫无意义,而是指普通Bean与FactoryBean的getObject方法产生了循环依赖,因为FactoryBean最终产生的对象是由getObject方法所产出。我们先来看看栗子吧~

假设工厂对象A依赖普通对象B,普通对象B依赖普通对象A。

小伙伴看到这里就可能问了,诶~你这不对呀,怎么成了「普通对象B依赖普通对象A」呢?不应该是工厂对象A吗?是这样的,在Spring中,由于普通对象A是由工厂对象A产生,所有在普通对象B想要获取普通对象A时,其实最终寻找调用的是工厂对象A的getObject方法,所以只要普通对象B依赖普通对象A就可以了,Spring会自动帮我们把普通对象B和工厂对象A联系在一起。

小伙伴,哦~

普通对象A

public class NormalBeanA {
  private NormalBeanB normalBeanB;
  public void setNormalBeanB(NormalBeanB normalBeanB) {
    this.normalBeanB = normalBeanB;
  }
}

工厂对象A

@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
  @Autowired
  private ApplicationContext context;
  @Override
  public NormalBeanA getObject() throws Exception {
    NormalBeanA normalBeanA = new NormalBeanA();
    NormalBeanB normalBeanB = context.getBean("normalBeanB", NormalBeanB.class);
    normalBeanA.setNormalBeanB(normalBeanB);
    return normalBeanA;
  }
  @Override
  public Class<?> getObjectType() {
    return NormalBeanA.class;
  }
}

普通对象B

@Component
public class NormalBeanB {
  @Autowired
  private NormalBeanA normalBeanA;
}

假设我们先创建对象A

由于FactoryBean和Bean的创建过程是一样的,只是多了步getObject,所以我们直接定位到调用getObject入口

if (mbd.isSingleton()) {
  // 开始创建bean
  sharedInstance = getSingleton(beanName, () -> {
    // 创建bean
    return createBean(beanName, mbd, args);
  });
  // 处理FactoryBean
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
  // 先尝试从缓存中获取,保证多次从工厂bean获取的bean是同一个bean
  object = getCachedObjectForFactoryBean(beanName);
  if (object == null) {
    // 从FactoryBean获取对象
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
  }
}
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
  // 加锁,防止多线程时重复创建bean
  synchronized (getSingletonMutex()) {
    // 这里是Double Check
    Object object = this.factoryBeanObjectCache.get(beanName);
    if (object == null) {
      // 获取bean,调用factoryBean的getObject()
      object = doGetObjectFromFactoryBean(factory, beanName);
    }
    // 又从缓存中取了一次,why? 我们慢慢分析
    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
      object = alreadyThere;
    }else{
      // ...省略初始化bean的逻辑...
      // 将获取到的bean放入缓存
      this.factoryBeanObjectCache.put(beanName, object);
    }
  }
}
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName){
  return factory.getObject();
}

现在,就走到了我们自定义的getObject方法,由于我们调用了context.getBean("normalBeanB", NormalBeanB.class),此时,将会去创建B对象,在创建过程中,先将B的早期对象放入三级缓存,紧接着填充属性,发现依赖了A对象,又要倒回来创建A对象,从而又回到上面的逻辑,再次调用我们自定义的getObject方法,这个时候会发生什么呢?

又要去创建B对象…(Spring:心好累)

但是!此时我们在创建B时,是直接通过getBean在缓存中获取到了B的早期对象,得以返回了!于是我们自定义的getObject调用成功,返回了一个完整的A对象!

但是此时FactoryBean的缓冲中还是什么都没有的。

// 又从缓存中取了一次
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
  object = alreadyThere;
}

这一次取alreadyThere必然是null,流程继续执行,将此时将获取到的bean放入缓存

this.factoryBeanObjectCache.put(beanName, object);

从FactoryBean获取对象的流程结束,返回到创建B的过程中,B对象此时的属性也得以填充,再返回到第一次创建A的过程,也就是我们第一次调用自定义的getObject方法,调用完毕,返回到这里

// 获取bean,调用factoryBean的getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
  object = alreadyThere;

那么,此时this.factoryBeanObjectCache.get(beanName)能从缓冲中拿到对象了吗?有没有发现,拿到了刚刚B对象填充属性时再次创建A对象放进去的!

所以,明白这里为什么要再次从缓存中获取了吧?就是为了解决由于循环依赖时调用了两次自定义的getObject方法,从而创建了两个不相同的A对象,保证我们返回出去的A对象唯一!

怕小伙伴晕了,画个图给大家

工厂Bean与普通Bean.png

5. 工厂Bean与工厂Bean之间

我们已经举例4种循环依赖的栗子,Spring都有所解决,那么有没有Spring也无法解决的循环依赖问题呢?

有的!就是这个FactoryBeanFactoryBean的循环依赖!

假设工厂对象A依赖工厂对象B,工厂对象B依赖工厂对象A,那么,这次的栗子会是什么样呢?

普通对象

public class NormalBeanA {
  private NormalBeanB normalBeanB;
  public void setNormalBeanB(NormalBeanB normalBeanB) {
    this.normalBeanB = normalBeanB;
  }
}
public class NormalBeanB {
  private NormalBeanA normalBeanA;
  public void setNormalBeanA(NormalBeanA normalBeanA) {
    this.normalBeanA = normalBeanA;
  }
}

工厂对象

@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
  @Autowired
  private ApplicationContext context;
  @Override
  public NormalBeanA getObject() throws Exception {
    NormalBeanA normalBeanA = new NormalBeanA();
    NormalBeanB normalBeanB = context.getBean("factoryBeanB", NormalBeanB.class);
    normalBeanA.setNormalBeanB(normalBeanB);
    return normalBeanA;
  }
  @Override
  public Class<?> getObjectType() {
    return NormalBeanA.class;
  }
}
@Component
public class FactoryBeanB implements FactoryBean<NormalBeanB> {
  @Autowired
  private ApplicationContext context;
  @Override
  public NormalBeanB getObject() throws Exception {
    NormalBeanB normalBeanB = new NormalBeanB();
    NormalBeanA normalBeanA = context.getBean("factoryBeanA", NormalBeanA.class);
    normalBeanB.setNormalBeanA(normalBeanA);
    return normalBeanB;
  }
  @Override
  public Class<?> getObjectType() {
    return NormalBeanB.class;
  }
}

首先,我们开始创建对象A,此时为调用工厂对象A的getObject方法,转而去获取对象B,便会走到工厂对象B的getObject方法,然后又去获取对象A,又将调用工厂对象A的getObject,再次去获取对象B,于是再次走到工厂对象B的getObject方法…此时,已经历了一轮循环,却没有跳出循环的迹象,妥妥的死循环了。

我们画个图吧~

工厂Bean与工厂Bean.png

没错!这个图就是这么简单,由于始终无法创建出一个对象,不管是早期对象或者完整对象,使得两个工厂对象反复的去获取对方,导致陷入了死循环。

那么,我们是否有办法解决这个问题呢?

我的答案是无法解决,如果有想法的小伙伴也可以自己想一想哦~

我们发现,在发生循环依赖时,只要循环链中的某一个点可以先创建出一个早期对象,那么在下一次循环时,就会使得我们能够获取到早期对象从而跳出循环!

而由于工厂对象与工厂对象间是无法创建出这个早期对象的,无法满足跳出循环的条件,导致变成了死循环。

那么此时Spring中会抛出一个什么样的异常呢?

当然是栈溢出异常啦!两个工厂对象一直相互调用,不断开辟栈帧,可不就是栈溢出有木有~

6. 工厂对象与代理对象

上面的情况是无法解决循环依赖的,那么这个情况可以解决吗?

答案是可以的!

我们分析了,一个循环链是否能够得到终止,关键在于是否能够在某个点创建出一个早期对象(临时对象),而代理对象在doCreateBean时,是会生成一个早期对象放入三级缓存的,于是该循环链得以终结。

具体过程我这里就不再细分析了,就交由小伙伴自己动手吧~

总结

以上我们一共举例了6种情况,通过分析,总结出这样一条定律:

在发生循环依赖时,判断一个循环链是否能够得到终止,关键在于是否能够在某个点创建出一个早期对象(临时对象),那么在下一次循环时,我们就能通过该早期对象进而跳出(打破)循环!

通过这样的定律,我们得出工厂Bean与工厂Bean之间是无法解决循环依赖的,那么还有其他情况无法解决循环依赖吗?

有的!以上的例子举的都是单例的对象,并且都是通过set方法形成的循环依赖。

假使我们是由于构造方法形成的循环依赖呢?是否有解决办法吗?

没有,因为这并不满足我们得出的定律

无法执行完毕构造方法,自然无法创建出一个早期对象。

假使我们的对象是多例的呢?

也不能,因为多例的对象在每次创建时都是创建新的对象,即使能够创建出早期对象,也不能为下一次循环所用!

好了,本文就到这里结束了,希望小伙伴们有所收获~

Spring IOC的核心部分到此篇就结束了,下一篇就让我们进行AOP之旅吧~

目录
相关文章
|
1天前
|
人工智能 Java Spring
Spring Boot循环依赖的症状和解决方案
Spring Boot循环依赖的症状和解决方案
|
1天前
|
缓存 Java 开发工具
【spring】如何解决循环依赖
【spring】如何解决循环依赖
13 0
|
1天前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
24 0
|
1天前
|
canal 缓存 关系型数据库
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
Spring Boot整合canal实现数据一致性解决方案解析-部署+实战
|
1天前
|
存储 缓存 Java
【spring】06 循环依赖的分析与解决
【spring】06 循环依赖的分析与解决
9 1
|
1天前
|
缓存 Java 数据库
第8章 Spring Security 的常见问题与解决方案(2024 最新版)(下)
第8章 Spring Security 的常见问题与解决方案(2024 最新版)
31 0
|
1天前
|
安全 Java 数据安全/隐私保护
第8章 Spring Security 的常见问题与解决方案(2024 最新版)(上)
第8章 Spring Security 的常见问题与解决方案(2024 最新版)
35 0
|
1天前
|
存储 缓存 Java
Spring解决循环依赖
Spring解决循环依赖
|
1天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
56 0
|
1天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
138 0