分享更有价值
被信任是一种快乐

怎么解决Spring循环依赖问题

文章页正文上

本篇内容介绍了“怎么解决Spring循环依赖问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言循环依赖问题,算是一道烂大街的面试题了,解毒之前,我们先来回顾两个知识点:初学 Spring 的时候,我们就知道 IOC,控制反转么,它将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,不需要我们手动去各种 new XXX。尽管是 Spring 管理,不也得创建对象吗, Java 对象的创建步骤很多,可以 new XXX、序列化、clone() 等等, 只是 Spring 是通过反射 + 工厂的方式创建对象并放在容器的,创建好的对象我们一般还会对对象属性进行赋值,才去使用,可以理解是分了两个步骤。好了,对这两个步骤有个印象就行,接着我们进入循环依赖,先说下循环依赖的概念什么是循环依赖所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖。更或者是自己依赖自己。它们之间的依赖关系如下:这里以两个类直接相互依赖为例,他们的实现代码可能如下:配置信息如下(用注解方式注入同理,只是为了方便理解,用了配置文件):Spring 启动后,读取如上的配置文件,会按顺序先实例化 A,但是创建的时候又发现它依赖了 B,接着就去实例化 B ,同样又发现它依赖了 A ,这尼玛咋整?无限循环呀Spring “肯定”不会让这种事情发生的,如前言我们说的 Spring 实例化对象分两步,第一步会先创建一个原始对象,只是没有设置属性,可以理解为”半成品”—— 官方叫 A 对象的早期引用(EarlyBeanReference),所以当实例化 B 的时候发现依赖了 A, B 就会把这个“半成品”设置进去先完成实例化,既然 B 完成了实例化,所以 A 就可以获得 B 的引用,也完成实例化了,这其实就是 Spring 解决循环依赖的思想。不理解没关系,先有个大概的印象,然后我们从源码来看下 Spring 具体是怎么解决的。源码解毒代码版本:5.0.16.RELEASE在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前, 必须对它进行实例化。只有在容器实例化后,才可以从 IOC 容器里获取 Bean 实例并使用,循环依赖问题也就是发生在实例化 Bean 的过程中的,所以我们先回顾下获取 Bean 的过程。获取 Bean 流程Spring IOC 容器中获取 bean 实例的简化版流程如下(排除了各种包装和检查的过程)大概的流程顺序(可以结合着源码看下,我就不贴了,贴太多的话,呕~呕呕,想吐):流程从 getBean 方法开始,getBean 是个空壳方法,所有逻辑直接到 doGetBean 方法中transformedBeanName 将 name 转换为真正的 beanName(name 可能是 FactoryBean 以 & 字符开头或者有别名的情况,所以需要转化下)然后通过 getSingleton(beanName) 方法尝试从缓存中查找是不是有该实例 sharedInstance(单例在 Spring 的同一容器只会被创建一次,后续再获取 bean,就直接从缓存获取即可)如果有的话,sharedInstance 可能是完全实例化好的 bean,也可能是一个原始的 bean,所以再经 getObjectForBeanInstance 处理即可返回当然 sharedInstance 也可能是 null,这时候就会执行创建 bean 的逻辑,将结果返回第三步的时候我们提到了一个缓存的概念,这个就是 Spring 为了解决单例的循环依赖问题而设计的 三级缓存这三级缓存的作用分别是:singletonObjects:完成初始化的单例对象的 cache,这里的 bean 经历过 实例化->属性填充->初始化 以及各种后置处理(一级缓存)earlySingletonObjects:存放原始的 bean 对象(完成实例化但是尚未填充属性和初始化),仅仅能作为指针提前曝光,被其他 bean 所引用,用于解决循环依赖的 (二级缓存)singletonFactories:在 bean 实例化完之后,属性填充以及初始化之前,如果允许提前曝光,Spring 会将实例化后的 bean 提前曝光,也就是把该 bean 转换成beanFactory 并加入到 singletonFactories(三级缓存)我们首先从缓存中试着获取 bean,就是从这三级缓存中查找如果缓存没有的话,我们就要创建了,接着我们以单例对象为例,再看下创建 bean 的逻辑(大括号表示内部类调用方法):1.创建 bean 从以下代码开始,一个匿名内部类方法参数(总觉得 Lambda 的方式可读性不如内部类好理解)getSingleton() 方法内部主要有两个方法2.getObject() 匿名内部类的实现真正调用的又是 createBean(beanName, mbd, args)3.往里走,主要的实现逻辑在 doCreateBean方法,先通过 createBeanInstance 创建一个原始 bean 对象4.接着 addSingletonFactory 添加 bean 工厂对象到 singletonFactories 缓存(三级缓存)5.通过 populateBean 方法向原始 bean 对象中填充属性,并解析依赖,免费云主机、域名假设这时候创建 A 之后填充属性时发现依赖 B,然后创建依赖对象 B 的时候又发现依赖 A,还是同样的流程,又去 getBean(A),这个时候三级缓存已经有了 beanA 的“半成品”,这时就可以把 A 对象的原始引用注入 B 对象(并将其移动到二级缓存)来解决循环依赖问题。这时候 getObject() 方法就算执行结束了,返回完全实例化的 bean6.最后调用 addSingleton 把完全实例化好的 bean 对象放入 singletonObjects 缓存(一级缓存)中,打完收工Spring 解决循环依赖建议搭配着“源码”看下边的逻辑图,更好下饭流程其实上边都已经说过了,结合着上图我们再看下具体细节,用大白话再捋一捋:鸿蒙官方战略合作共建——HarmonyOS技术社区Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化每次创建 bean 之前,我们都会从缓存中查下有没有该 bean,因为是单例,只能有一个当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了 beanB,接着就又去创建 beanB,同样的流程,创建完 beanB 填充属性时又发现它依赖了 beanA,又是同样的流程,不同的是,这时候可以在三级缓存中查到刚放进去的原始对象 beanA,所以不需要继续创建,用它注入 beanB,完成 beanB 的创建既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成这就是单例模式下 Spring 解决循环依赖的流程了。但是这个地方,不管是谁看源码都会有个小疑惑,为什么需要三级缓存呢,我赶脚二级他也够了呀革命尚未成功,同志仍需努力跟源码的时候,发现在创建 beanB 需要引用 beanA 这个“半成品”的时候,就会触发”前期引用”,即如下代码:singletonFactory.getObject() 是一个接口方法,这里具体的实现方法在这个方法就是 Spring 为什么使用三级缓存,而不是二级缓存的原因,它的目的是为了后置处理,如果没有 AOP 后置处理,就不会走进 if 语句,直接返回了 exposedObject ,相当于啥都没干,二级缓存就够用了。所以又得出结论,这个三级缓存应该和 AOP 有关系,继续。在 Spring 的源码中getEarlyBeanReference 是 SmartInstantiationAwareBeanPostProcessor接口的默认方法,真正实现这个方法的只有**AbstractAutoProxyCreator** 这个类,用于提前曝光的 AOP 代理。这么说有点干,来个小 demo 吧,我们都知道 Spring AOP、事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器来自动完成的。也就是说 Spring 最终给我们放进容器里面的是一个代理对象,而非原始对象,假设我们有如下一段业务代码:此 Service 类使用到了事务,所以最终会生成一个 JDK 动态代理对象 Proxy。刚好它又存在自己引用自己的循环依赖,完美符合我们的场景需求。我们再自定义一个后置处理,来看下效果:可以看到,调用方法栈中有我们自己实现的 HelloProcessor,说明这个 bean 会通过 AOP 代理处理。再从源码看下这个自己循环自己的 bean 的创建流程:自我解惑:问:还是不太懂,为什么这么设计呢,即使有代理,在二级缓存代理也可以吧 | 为什么要使用三级缓存呢?我们再来看下相关代码,假设我们现在是二级缓存架构,创建 A 的时候,我们不知道有没有循环依赖,所以放入二级缓存提前暴露,接着创建 B,也是放入二级缓存,这时候发现又循环依赖了 A,就去二级缓存找,是有,但是如果此时还有 AOP 代理呢,我们要的是代理对象可不是原始对象,这怎么办,只能改逻辑,在第一步的时候,不管3721,所有 Bean 统统去完成 AOP 代理,如果是这样的话,就不需要三级缓存了,但是这样不仅没有必要,而且违背了 Spring 在结合 AOP 跟 Bean 的生命周期的设计。所以 Spring “多此一举”的将实例先封装到 ObjectFactory 中(三级缓存),主要关键点在 getObject() 方法并非直接返回实例,而是对实例又使用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法对 bean 进行处理,也就是说,当 Spring 中存在该后置处理器,所有的单例 bean 在实例化后都会被进行提前曝光到三级缓存中,但是并不是所有的 bean 都存在循环依赖,也就是三级缓存到二级缓存的步骤不一定都会被执行,有可能曝光后直接创建完成,没被提前引用过,就直接被加入到一级缓存中。因此可以确保只有提前曝光且被引用的 bean 才会进行该后置处理。再问:AOP 代理对象提前放入了三级缓存,没有经过属性填充和初始化,这个代理又是如何保证依赖属性的注入的呢?这个又涉及到了 Spring 中动态代理的实现,不管是cglib代理还是jdk动态代理生成的代理类,代理时,会将目标对象 target 保存在最后生成的代理 $proxy 中,当调用 $proxy 方法时会回调 h.invoke,而 h.invoke 又会回调目标对象 target 的原始方法。所有,其实在 AOP 动态代理时,原始 bean 已经被保存在 提前曝光代理中了,之后 原始 bean 继续完成属性填充和初始化操作。因为 AOP 代理$proxy中保存着 traget 也就是是 原始bean 的引用,因此后续 原始bean 的完善,也就相当于Spring AOP中的 target 的完善,这样就保证了 AOP 的属性填充与初始化了!非单例循环依赖看完了单例模式的循环依赖,我们再看下非单例的情况,假设我们的配置文件是这样的:启动 Spring,结果如下:

相关推荐: python怎么通过Matplotlib绘制常见的图形

今天小编给大家分享一下python怎么通过Matplotlib绘制常见的图形的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。以上就是“python…

文章页内容下
赞(0) 打赏
版权声明:本站采用知识共享、学习交流,不允许用于商业用途;文章由发布者自行承担一切责任,与本站无关。
文章页正文下
文章页评论上

云服务器、web空间可免费试用

宝塔面板主机、支持php,mysql等,SSL部署;安全高速企业专供99.999%稳定,另有高防主机、不限制内容等类型,具体可咨询QQ:360163164,Tel同微信:18905205712

主机选购导航云服务器试用

登录

找回密码

注册