开心一刻

  那天知道她结婚了,我整整一个晚上没睡觉,开了三百公里的车来到她家楼下,缓缓的抽了一支烟......

  天渐渐凉了,响起了鞭炮声,迎亲车队到了,那天披着婚纱的她很美,真的很美!

  我跟着迎亲车队开了几公里的时候,收到了她的信息:别送了,别送了,你的手扶拖拉机太响了 ......

前情回顾

  楼主一而再,再而三的折腾循环依赖,你们不烦,楼主自己都烦了,如果你们实在是受不了,那就...

  言归正传,虽然确实有点像懒婆娘的裹脚布,又臭又长,但确实还是有点东西的,只要大家坚持看完,肯定会有收获的!

  我们先回顾下前三探

  一探

  Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗 中讲到了循环依赖问题

   Spring 通过三级缓存解决 setter 循环依赖

  一级缓存 singletonObjects 存的是对外暴露的对象,也就是我们应用真正用到的对象

  二级缓存 earlySingletonObjects 存的是半成品对象或半成品对象的代理对象,用于处理循环依赖的对象创建问题

  三级缓存 singletonFactories 存的是创建对象的工厂方法,用于处理存在 AOP + 循环依赖的对象创建问题

  着重分析了是否一定需要三级缓存来解决循环依赖问题

  二探

   Spring 不能处理构造方法的循环依赖,也不能处理原型循环依赖

  再探循环依赖 → Spring 是如何判定原型循环依赖和构造方法循环依赖的,从源码的角度分析了 Spring 是如何鉴别构造方法循环依赖、原型循环依赖的

   Set<String> singletonsCurrentlyInCreation 会记录当前正在创建中的实例名称, Spring 创建实例对象之前,会判断 singletonsCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示产生构造方法循环依赖了

   ThreadLocal<Object> prototypesCurrentlyInCreation 会记录当前线程正在创建中的原型实例名称, Spring 创建原型实例对象之前,会判断 prototypesCurrentlyInCreation 中是否存在该实例的名称,如果存在则表示产生原型循环依赖了

  三探

  三探循环依赖 → 记一次线上偶现的循环依赖问题,从源码的角度分析了这次偶现问题可能出现的原因

   BeanDefinition 的扫描顺序:以启动类为起点,扫描启动类同级目录下的所有文件夹,按文件夹名升序顺序进行扫描,会递归扫描每个文件夹,文件扫描也是按文件名升序顺序进行

   BeanDefinition 覆盖, @Configuration + @Bean 修饰的 BeanDefinition 会覆盖 @Component 修饰的 BeanDefinition , BeanDefinition 的覆盖并不影响 BeanDefinition 的扫描

   Bean 的实例化顺序,理论上来讲,先被扫描到的就先被实例化,但实例化过程中的属性填充会打乱这个顺序,会将被依赖的对象提前实例化

  一通分析下来,虽说没能找到问题的真正原因,但至少知道了如何去规避这个问题,如何正确的书写规范的代码

问题复现

  经过前面三探,楼主以为对 Spring 的循环依赖已经拿捏的死死的了,然而当他出现后,楼主才发现,不是她离不开我,而是我离不开她了

  我们来看看循环依赖和 BeanPostProcessor 是如何产生爱情的火花的

   SpringBoot 版本 2.0.3.RELEASE ,示例代码地址:spring-circular-beanpostprocessor

  我们只需要关注三个类

  依赖很简单, ServiceAImpl 依赖 ServiceBImpl , ServiceBImpl 也依赖 ServiceAImpl ,这种循环依赖,楼主自认为拿捏的死死的

  直到 BeanPostProcessor 的出现,循环依赖决定不再迁就,她俩的爱情就产生了

  她俩的爱情信息:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceAImpl': Bean with name 'serviceAImpl' has been injected into other beans [serviceBImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

  此刻,楼主才明白,小丑竟是我自己!

问题分析

  其实她俩的爱情信息已经提示的很明显了,楼主再忍痛翻译一下: serviceAImpl 作为循环依赖的一部分注入到了 serviceBImpl 后,又被包装了,这就意味着 serviceBImpl 引用的不是最终版本的 serviceAImpl

  关于 BeanPostProcessor ,楼主不想过多介绍,大家可以查看:Spring拓展接口之BeanPostProcessor,我们来看看它的底层实现

  从错误堆栈信息,我们可以追踪到 Spring 报错的代码

   因为 ServiceAImpl 比 ServiceBImpl 先被扫描,所以 serviceAImpl 先被实例化,实例化过程如下

  此时一切都正常,问题就出在 serviceAImpl 填充属性serviceBImpl 完成之后,我们来 debug 下

  从 debug 结果可以看到, ServiceBImpl 的实例对象 ServiceBImpl@5171 中注入的 ServiceAImpl 对象是 ServiceAImpl@5017

  而经过 initializeBean(beanName, exposedObject, mbd); 后, Spring 暴露出来的 ServiceAImpl 的最终对象是 $Proxy53@5212

  这就导致 ServiceBImpl@5171 中注入的 ServiceAImpl@5017 并不是最终版本的 ServiceAImpl ,她们的爱情就这么产生了

问题处理

  面对这样的问题,我们可以怎么处理了

  @Lazy

  通过 @Lazy 延迟注入,在真正使用到的时候才进行注入

  在任意一个属性上加 @Lazy 即可,例如

  或者

  或者两个都加上 @Lazy

  SmartInstantiationAwareBeanPostProcessor

  弃用 BeanPostProcessor ,改用 SmartInstantiationAwareBeanPostProcessor

  重写的方法是: getEarlyBeanReference ,而非 postProcessAfterInitialization 方法,提前暴露代理对象

  也就是说在 ServiceAImpl 对象填充属性(populateBean(beanName, mbd, instanceWrapper))之前,就将代理对象提前暴露到第三级缓存中

  后续给 ServiceBImpl 对象填充 serviceAImpl 属性时,就用第三级缓存中的 ServiceAImpl 代理对象

  剔除循环依赖

  循环依赖本就不合理,项目中应尽量避免

  至于如何剔除,无法一概而论,需要大家自己去琢磨了

总结

  循环依赖

  虽说 Spring 通过三级缓存解决了 setter 方式的循环依赖,但这不能成为我们有恃无恐的理由

  循环依赖本就不合理,尽量去规避

  真实项目问题

  相信很多小伙伴会有这样的疑问:楼主,你是怎么就让 循环依赖 遇上 BeanPostProcessor ?

  因为已有代码的不规范,导致很多地方都产生了循环依赖,而最近又引入 Shareding-JDBC 做分库,而 Shareding-JDBC 又通过 BeanPostProcessor 来生成代理对象

  就这样,她俩就相遇了

四探循环依赖 → 当循环依赖遇上 BeanPostProcessor,爱情可能就产生了!的更多相关文章

  1. BFS (1)算法模板 看是否需要分层 (2)拓扑排序——检测编译时的循环依赖 制定有依赖关系的任务的执行顺序 djkstra无非是将bfs模板中的deque修改为heapq

    BFS模板,记住这5个: (1)针对树的BFS 1.1 无需分层遍历 from collections import deque def levelOrderTree(root): if not ro ...

  2. 四、Oracle loop循环、while循环、for循环、if选择和case选择、更改读取数据、游标、触发器、存储过程

    数据库的设计(DataBase Design): 针对于用户特定的需求,然后我们创建出来一个最使用而且性能高的数据库! 数据库设计的步骤: 01.需求分析 02.概念结构设计 03.逻辑结构设计 04 ...

  3. day4 四、流程控制之if判断、while循环、for循环

    一.if判断 1.语法一: if 条件: 条件成立时执行的子代码块 代码1 代码2 代码3 示例: sex='female' age= is_beautiful=True and age < a ...

  4. 浅谈集合框架四——集合扩展:集合循环输出方式及list输出方式的效率对比

    最近刚学完集合框架,想把自己的一些学习笔记与想法整理一下,所以本篇博客或许会有一些内容写的不严谨或者不正确,还请大神指出.初学者对于本篇博客只建议作为参考,欢迎留言共同学习. 之前有介绍集合框架的体系 ...

  5. Python基础学习参考(四):条件与循环

    在实际的开发中,想要实现某些功能或者需求,里面必然涉及到一些逻辑,复杂的也好简单也好,那么,通过python语法如何实现呢?这就涉及到了条件与循环.很显然绝大多数的语言都有条件和循环的语法,pytho ...

  6. Spring框架系列(四)--IOC控制反转和DI依赖注入

    背景: 如果对象的引用或者依赖关系的管理由具体对象完成,代码的耦合性就会很高,代码测试也变得困难.而IOC可以很好的解决这个问题,把这 些依赖关系交给框架或者IOC容器进行管理,简化了开发. IOC是 ...

  7. C# 循环语句 for循环

    循环:反复执行某段代码. 循环四要素:初始条件,循环条件,循环体,状态改变.for(初始条件;循环条件;状态改变){ 循环体} 给出初始条件,先判断是否满足循环条件,如果不满足条件则跳过for语句,如 ...

  8. for循环、for循环嵌套

    循环:反复执行某段代码. 循环四要素:初始条件,循环条件,循环体,状态改变. 循环的最后一句:循环条件不再满足. 1.找出100以内与7有关的数并打印:(1).从1找到100(2).找出与7有关的数 ...

  9. WPF 依赖属性与依赖对象

    在介绍依赖属性之前,我先介绍下属性的历史 属性的历史:      早期C++的类中,只有字段及方法,暴露数据靠的是方法, 但是字段直接暴露会不安全,所以才用方法来暴露,在设置的时候加些约束,在MFC中 ...

随机推荐

  1. vue实现PC端分辨率适配

    lib-flexible + px2rem Loader lib-flexible 阿里伸缩布局方案 px2rem-loader:px转rem: 依赖 首先需要安装 vue-cli 脚手架,这里我安装 ...

  2. kafka学习笔记(六)kafka的controller模块

    概述 今天我们主要看一下kafka的controller的代码,controller代码是kafka的非常重要的代码,需要我们深入学习.从某种意义上来说,它是kafka最核心的组件,一方面,他要为集群 ...

  3. WAFW00F waf识别工具 源码学习

    我实习工作的第一个任务根据已有的java waf识别工具 实现了一个python的waf识别工具 代码结构非常乱 仅仅达到了能用的水平. 顶头svp推荐这个项目当时我已经写好了开始用了自己的 稍微看了 ...

  4. 【PTA】6-1 **删除C程序中的注释 (31 分)

    请你编写一个函数,将C语言源程序中的注释全部删去. 函数原型 // 删除注释 void Pack(FILE *src, FILE *dst); 说明:参数 src 和 dst 均为文件指针,其中:sr ...

  5. 使用.NET 6开发TodoList应用(29)——实现静态字符串本地化功能

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在开发一些需要支持多种语言的应用程序时,我们需要根据切换的语言来对应展示一些静态的字符串字段,在本文中我们暂时不去讨论如何结合 ...

  6. 嵌入式学习第四步—C语言学习用软件安装

    学习一门计算机语言,不能光靠看书,最主要的是要动手联系.不记得从哪里看到过,要成为一名网络大牛,要有10万行以上的程序才是基础. 首先需要一个能够编辑程序的地方(IDE),经过大约10天的网上看各种视 ...

  7. Windows系统安装和office版本兼容

    MSDN, I tell you 下载windows10系统各种纯净镜像.但是还是推荐使用官网修改参数显示出下载链接,下载windows10最新版. 在最近几次重做系统中,PE工具似乎无法识别从MSD ...

  8. Tomcat-部署web工程方式

    Tomcat(部署web工程) 第一种方法:只需要把web工程的目录拷贝到Tomcat的webapps目录下即可 1,在webapps目录下创建一个book工程, 2,或者把做的工程内容拷贝到weba ...

  9. mac 更新到big sur 后,parallels虚拟机的一些问题:由于您尚未获得访问其中一些文件的授权,所以您不能恢复“Windows 10

    由于您尚未获得访问其中一些文件的授权,所以您不能恢复"Windows 10 Mac上使用PD虚拟机,打开系统时提示"由于您尚未获得访问其中一些文件的授权,所以您不能恢复" ...

  10. linux信号 SIGINT SIGTERM SIGKILL

    三者都是结束/终止进程运行. 1.SIGINT SIGTERM区别 前者与字符ctrl+c关联,后者没有任何控制字符关联. 前者只能结束前台进程,后者则不是. 2.SIGTERM SIGKILL的区别 ...