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之旅吧~

目录
相关文章
|
29天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
51 1
|
9天前
|
Java Nacos Sentinel
Spring Cloud Alibaba:一站式微服务解决方案
Spring Cloud Alibaba(简称SCA) 是一个基于 Spring Cloud 构建的开源微服务框架,专为解决分布式系统中的服务治理、配置管理、服务发现、消息总线等问题而设计。
111 13
Spring Cloud Alibaba:一站式微服务解决方案
|
29天前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
13天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
30 2
|
13天前
|
安全 Java API
实现跨域请求:Spring Boot后端的解决方案
本文介绍了在Spring Boot中处理跨域请求的三种方法:使用`@CrossOrigin`注解、全局配置以及自定义过滤器。每种方法都适用于不同的场景和需求,帮助开发者灵活地解决跨域问题,确保前后端交互顺畅与安全。
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
33 1
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
32 1
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
25 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
216 2
|
23天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
39 1
SpringBoot入门(7)- 配置热部署devtools工具