Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?

简介: 在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。

前言


在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。

同样在上一节留下了疑问:

  1. 循环依赖为什么要使用三级缓存?而不是使用二级缓存?
  2. AOP 动态代理对循环依赖的有没有什么影响?

本篇文章也是围绕上面的内容进行展开。

笔记也在不断整理,之前可能会有点杂乱。


循序渐进,看一看什么是循环依赖?

开始先简单回顾一下 Bean 的创建过程,当然小伙伴也可以直接阅读『 单例 Bean 的创建 』这篇文章。

不过考虑到阅读本文前再阅读上一篇文章、Debug 等等,会比较耗时,所以本篇文章前面一小部分会先对之前的文章内容做简要概括,也相当于对我自己学习的知识进行一个总结。

先来回顾一下三级缓存的概念。

singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

singletonFactories: 三级缓存,存储 singletonFactory。


Bean 的创建过程

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
}

网络异常,图片无法展示
|

通过上面的流程,可以看出 Spring 在创建 Bean 的过程中重点是在 AbstractAutowireCapableBeanFactory 中的以下三个步骤:

  1. 实例化 createBeanInstance: 其中实例化 Bean 并对 Bean 进行赋值,像例子中的 fieldA 字段在这里就会赋值。
  2. 属性注入 populateBean: 可以理解为对 Bean 里面的属性进行赋值。(会依赖其他 Bean)
  3. 初始化 initializeBean: 执行初始化和 Bean 的后置处理器。

实例化赋值源码可以阅读:

BeanUtils.instantiateClass(constructorToUse)


如果要依赖其他 Bean 呢?

那如果 CircularServiceA 依赖了其他 Bean 呢?

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
    @Autowired
    private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
}

网络异常,图片无法展示
|

当 A 依赖了 B 的时候,在 createBeanInstance 这一步,并不会对 B 进行属性赋值。

而是在 populatedBean 这里查找依赖项,并创建 B。


循环依赖下的创建过程

循环依赖的场景,在上一篇文章已经有所讲解,这里仅仅画图说明一下。

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
    @Autowired
    private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
    @Autowired
    private CircularServiceA circularServiceA;
}

网络异常,图片无法展示
|

在 A 和 B 循环依赖的场景中:

B populatedBean 查找依赖项 A 的时候,从一级缓存中虽然未获取到 A,但是发现 A 在创建中。

此时,从三级缓存中获取 A 的 singletonFactory 调用工厂方法,创建 getEarlyBeanReference A 的早期引用并返回。

B 引用到 A ,B 就可以初始化完毕,然后 A 同样也可以初始化完毕了。


二级缓存能否解决循环依赖

通过上面的图,仔细分析一下,其实把二级缓存拿掉,在 B 尝试获取 A 的时候直接返回 A 的实例,是不是也是可以的?

答案是:可以的!

但是为什么还是用三级缓存呢?

网上的很多资料说是和动态代理有关系,那就从动态代理的方面继续往下分析分析。


动态代理的场景

在 JavaConfig(配置类) 上添加 @EnableAspectJAutoProxy 注解,开启 AOP ,通过 Debug 循序渐进看一看动态代理对循环依赖的影响。


动态代理下,Bean 的创建过程

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
    public void methodA() {
        System.out.println("方法 A 执行");
    }
}
@Aspect
@Component
public class AspectA {
    @Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())")
    public void beforeA() {
        System.out.println("beforeA 执行");
    }
}

只有 A 的情况下,给 A 添加切面,开始 Debug。

前面的流程都相同,在 initializeBean 开始出现差异。

这一步需要初始化 Bean 并执行 Bean 的后置处理器。

网络异常,图片无法展示
|

其中有一个处理器为: AnnotationAwareAspectJAutoProxyCreator 其实就是加的注解切面,会跳转到 AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法

网络异常,图片无法展示
|

如图所示:wrapIfNecessary 方法会判断是否满足代理条件,是的话返回一个代理对象,否则返回当前 Bean。

后续调用 getProxy、createAopProxy 等等,最终执行到下面一部分。

网络异常,图片无法展示
|

最终会执行到这里,AOP 代理相关的就不细看了。

一路放行,直到 initializeBean 执行结束。

网络异常,图片无法展示
|

此时发现:A 被替换为了代理对象。

所以 doCreateBean 返回,以及后面放到一级缓存中的都是代理对象。

网络异常,图片无法展示
|


有循环依赖的动态代理

这一次把循环依赖打开:

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
    @Autowired
    private CircularServiceB circularServiceB;
    public void methodA() {
        System.out.println("方法 A 执行");
    }
}
@Aspect
@Component
public class AspectA {
    @Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())")
    public void beforeA() {
        System.out.println("beforeA 执行");
    }
}
@Service
public class CircularServiceB {
    @Autowired
    private CircularServiceA circularServiceA;
    public void methodB() {
    }
}
@Aspect
@Component
public class AspectB {
    @Before("execution(public void com.liuzhihang.circular.CircularServiceB.methodB())")
    public void beforeB() {
        System.out.println("beforeB 执行");
    }
}

开始 Debug,前面的一些列流程,都和正常的没有什么区别。而唯一的区别在于,创建 B 的时候,需要从三级缓存获取 A。

此时在 getSingleton 方法中会调用:singletonObject = singletonFactory.getObject();

网络异常,图片无法展示
|

有时会比较疑惑 singletonFactory.getObject() 调用的是哪里?

网络异常,图片无法展示
|

所以这一块调用的是 getEarlyBeanReference,开始遍历执行 BeanPostProcessor

网络异常,图片无法展示
|

网络异常,图片无法展示
|

看到 wrapIfNecessary 就明白了吧!这块会获取一个代理对象


也就是说此时返回,并放到二级缓存的是一个 A 的代理对象。

这样 B 就创建完毕了!

到 A 开始初始化并执行后置处理器了!因为 A 也有代理,所以 A 也会执行到 postProcessAfterInitialization 这一部分!

网络异常,图片无法展示
|

但是在执行 wrapIfNecessary 之前,会先判断代理对象缓存是否有 A 了。

this.earlyProxyReferences.remove(cacheKey) != bean

但是这块获取到的是 A 的代理对象。肯定是 false 。 所以不会再生成一次 A 的代理对象。

网络异常,图片无法展示
|


总结


可以看到,循环依赖下,有没有代理情况下的区别就在:

singletonObject = singletonFactory.getObject();

在循环依赖发生的情况下 B 中的 A 赋值时:

  1. 无代理:getObject 直接返回原来的 Bean
  2. 有代理:getObject 返回的是代理对象

然后都放到二级缓存


为什么要三级缓存?

  1. 假设去掉三级缓存

去掉三级缓存之后,Bean 直接创建 earlySingletonObjects, 看着好像也可以。

如果有代理的时候,在 earlySingletonObjects 直接放代理对象就行了。

但是会导致一个问题:在实例化阶段就得执行后置处理器,判断有 AnnotationAwareAspectJAutoProxyCreator 并创建代理对象

这么一想,是不是会对 Bean 的生命周期有影响。


同样,先创建 singletonFactory 的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。

  1. 假设去掉二级缓存

如果去掉了二级缓存,则需要直接在 singletonFactory.getObject() 阶段初始化完毕,并放到一级缓存中。

网络异常,图片无法展示
|

那有这么一种场景,B 和 C 都依赖了 A。

要知道在有代理的情况下 singletonFactory.getObject() 获取的是代理对象。

网络异常,图片无法展示
|

而多次调用 singletonFactory.getObject() 返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。

那如果获取 B 到之后直接放到一级缓存,然后 C 再获取呢?

😳 ……

一级缓存放的是已经初始化完毕的 Bean,要知道 A 依赖了 B 和 C ,A 这时候还没有初始化完毕。


小结


循环依赖的场景有很多,本文只是通过 Debug ,来了解到循环依赖和 AOP 之间的关系,以及了解到为什么要用三级缓存。

当然,Spring 设计之初是什么样子的?如何一步一步发展成现在这种的?

肯定是不能慢慢去研究了,所以只能以现在的版本,去揣测作者的意图。

不足之处,多多指正。

目录
相关文章
|
4月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
21天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
41 2
|
29天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
43 4
|
3月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
232 24
|
2月前
|
Java 数据安全/隐私保护 Spring
Spring进阶:初识动态代理
本文介绍了Spring框架中AOP切面编程的基础——动态代理。通过定义Vehicle接口及其实现类Car和Ship,展示了如何使用动态代理在不修改原代码的基础上增强功能。文章详细解释了动态代理的工作原理,包括通过`Proxy.newProxyInstance()`方法创建代理对象,以及`InvocationHandler`接口中的`invoke()`方法如何处理代理对象的方法调用。最后,通过一个测试类`TestVehicle`演示了动态代理的具体应用。
|
3月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
2月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
72 1
|
3月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
46 4
|
4月前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。