写点什么

spring 的循环依赖

用户头像
卢卡多多
关注
发布于: 2 小时前

晚上好,我是卢卡,对于很多新手,项目中刚开始使用的框架就是 spring,但是 spring 你到底了解多少,对于数据操作的底层对象调用,如何减少重复造轮子,这些都是我们要掌握的问题。本期,我们就利用 spring 中如何解决,依赖注入(Dependency Injection),循环依赖怎么解决;

循环依赖


循环依赖,通常是指 Bean 对象之间的互相依赖,比如 A 对象创建需要 B 对象,也就是 A 依赖于 B,依赖类推,B 依赖于 C,C 依赖于 A, 形成一个完整的闭环,以下图为:


    多个bean之间互相依赖,形成一个闭环====>循环依赖
复制代码


说说 spring 容器中的循环依赖:


一定是默认的单例bean中,属性互相引用的场景,也就是说 spring初始化容器方法refresh()中,对待bean,都是初始化后的单例对象==>也就是scope=singleton
复制代码


每个对象是单例的对象,互相依赖的时候,可以直接调用,


spring 底层怎么保证 AB 循环依赖的问题?


所以说,对于循环多次依赖,我们要求的是,单例且 set 注入------>可以避免 BeanCurrentlyInCreationException 的问题


spring 循环依赖的注入方式


对于spring循环依赖中,有一个误区,通常可以分为构造器注入和set方法注入,两种注入方式会有影响,我们来找寻官网中的理解;
复制代码


实例化构造器注入带来的 beancurrentlyIncreationException 的问题;模拟 A 和 B 两个实例对象,在循环依赖中,利用构造器互相注入,看能否实现循环依赖:


创建三个类:


package com.atguowang.thirdinterview.spring.aroudyilai.constructor;


import org.springframework.stereotype.Component;


/**


  • @author shkstart

  • @create 2020-11-12 16:51*/@Componentpublic class ServiceA {

  • private ServiceB serviceB;

  • public ServiceA(ServiceB serviceB) {//当前私有的 serviceB--this.serviceB=serviceB;}


}


package com.atguowang.thirdinterview.spring.aroudyilai.constructor;


import org.springframework.stereotype.Component;


/**


  • @author shkstart

  • @create 2020-11-12 16:51*/@Componentpublic class ServiceB {

  • private ServiceA serviceA;

  • public ServiceB(ServiceA serviceA ) {this.serviceA=serviceA;}}


package com.atguowang.thirdinterview.spring.aroudyilai.constructor;


import sun.applet.Main;


/**


  • @author shkstart

  • @create 2020-11-12 16:51*/public class TestConstructor {public static void main(String[] args) {

  • }


}


结论:


因为我们要创建一个 A,就要在构造器加载过程中,加入一个 B,但是又 要重复创建 A,和 B;constructor 无法解决这个问题;


2:利用 set 注入进行 spring 的循环依赖注入 package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.SetInjection;


import org.springframework.stereotype.Component;


/**


  • @author lucas

  • @create 2020-12-12 17:13*/@Componentpublic class ServiceBB {

  • private ServiceAA serviceAA;

  • public void setServiceAA(ServiceAA serviceAA){this.serviceAA=serviceAA;System.out.println("B 中得到这个方法 A 的赋值");}}


package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.SetInjection;


import org.springframework.stereotype.Component;


/**


  • @author lucas

  • @create 2020-12-12 17:13*/@Componentpublic class ServiceBB {

  • private ServiceAA serviceAA;

  • public void setServiceAA(ServiceAA serviceAA){this.serviceAA=serviceAA;System.out.println("B 中得到这个方法 A 的赋值");}}


循环依赖 set 方法注入,可以依赖实现;


package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.SetInjection;


import sun.java2d.pipe.AAShapePipe;


/**


  • @author lucas

  • @create 2020-12-12 17:13*/public class TestSetInjection {

  • public static void main(String[] args) {/*** 测试 set 方法注入的循环依赖*/ServiceAA aa = new ServiceAA();ServiceBB bb = new ServiceBB();

  • }


}


上述是基于JAVA SE实现的代码,但是我们要寻找底层spring容器中,到底对于循环依赖怎么实现呢?
我们先确定一个概念,就是spring容器化;
复制代码


spring 容器化


可以把spring容器化,也就是存放bean对象,(实例化对象)可以相互复用的一个池化思想,举个例子,我每次要去买鱼和鸡肉,但是这个是俩对象,存在于两个菜市场,spring容器就作为中间点,把鱼肉和鸡肉都放入里面, 然后我要使用的时候,就从池子中直接取用,这样的好处是:
复制代码


减少通讯的资源损耗


节省对象复用


更好的控制对象


创建符合要求的初始化对象


 spring作为一个优秀的框架,极大程度上对于程序员来说,减少了创建对象,集中管理对象,复用等问题,做出了很大的贡献,减少了重复造轮子,也就是说,我们要创建一个对象的时候 ,我们不用new ,而是通过权限控制,IOC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权的转移给spring容器,更好的集中控制和管理对象;
复制代码


我们现在开始觊觎 spring 容器化来实现循环依赖到底怎么玩?


 这里我们就利用xml文件来实现,application.xml,两个对象A和B,互相依赖
复制代码


对象 A:


package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;


/**


  • @author maweibo

  • @create 2021-01-05 10:09

  • @description spring 容器中关于循环依赖的实现*/public class A {

  • private B b;

  • public B getB() {return b;}

  • public void setB(B b) {this.b = b;}

  • A(){System.out.println(" A -created- success");}


}


对象 B:


package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;


/**


  • @author maweibo

  • @create 2021-01-05 10:10

  • @description spring 容器中*/


public class B {


private A a;
public A getA() { return a;}
public void setA(A a) { this.a = a;}
B(){
System.out.println(" B -created- success");}
复制代码


}


package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;


/**


  • @author maweibo

  • @create 2021-01-05 10:10

  • @description set 方法实现*/


public class B {


private A a;
public A getA() { return a;}
public void setA(A a) { this.a = a;}
B(){
System.out.println(" B -created- success");}
复制代码


}


建议一下,set 方式注入是没问题的,然后我们在基础上添加 spring 容器的配置文件,注意看好添加的位置;


application.xml:


开始做 spring 容器实例化创建两个对象,进行循环依赖;


package com.atguowang.thirdinterview.spring.aroudyilai.depencyInjection.springIOC;


import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;


/**


  • @author maweibo

  • @create 2021-01-05 10:38

  • @description spring 单例容器化实现循环依赖*/public class ClientSpringcontainer {public static void main(String[] args) {

  • }}

  • 注意事项:

  • 第一次开始的时候,可能会遇见 bean 不存在,注意建立时候配置文件的位置,检查 A 和 B 的位置,我准备了链接,给你谈谈路,

  • 如何优雅的解决:


https://winterchen.blog.csdn.net/article/details/78425230?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control成功如下:


思考一下,是因为spring容器化对象的时候,scope=singleton,单例对象,要是变为scope=phototype ,(原型)也就是每次调用,都会创建一个新的A对象或者是B对象, 好汉们:试一把:我们将配置文件的scope属性改了;
复制代码


结果如图所示:


报出的错误,和我们之前构造器注入方式一致,都是 BeanCurrentlyInCreationException:bean 对象一致处于创建内层的循环中,发生异常,也就说当 scope 属性不是单例的时候,无法解决 spring 循环依赖的问题;


其实做到这里,我们只是知道了,spring循环依赖是利用单例spring容器化,或者是set方式注入,实现具体的循环依赖(circular dependencies ),但是我还是想知道,到底源码底层是如何实现的;相应的我查到了新名词,与spring循环依赖是有三级缓存实现的;


结论先记下:spring内部是根据三级缓存来实现循环依赖的;
复制代码


三级缓存


当面试官说道,spring循环依赖,你能提到三级缓存,那证明你绝对是拥有内功的,这属于源码范畴,也可以侧面了解到你的学习潜力,哈哈,开干:三级缓存主要是这个类; DefaultSingletonBeanRegistry;
复制代码


IDEA 的同学呢,可以那两次 shift 键,查询这个类的具体实现过程;


 这个类中其实是利用三个MAP,来实现三级缓存的;
何为三级缓存:
复制代码


package org.springframework.beans.factory.support;public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {protected static final Object NULL_OBJECT = new Object();protected final Log logger = LogFactory.getLog(this.getClass());private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); //一级 单例-成品 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); //三级--工厂 beanprivate final Map<String, Object> earlySingletonObjects = new HashMap(16); //二级 半成品


三级缓存只是适用于,对象是单例 bean 对象,每次创建 bean 对象;


注意事项:


   在java中对于实例化和初始化是两个概念:
实例化对象之后-->才能初始化对象属性


实例化: 相当于自身需要请求一块区域,但是只是在申请成功的过程中,其中放什么还是未知的数据


初始化:我们常说初始化--代表程序加载属性,各种数据,开始运行的过程
复制代码


scope=phototype


关于为什么scope=phototype时候,为什么会解决不了循环依赖?
原型对象-->多例模式,每次获得bean都会生成一个新的对象
复制代码


解答:


对于每次创建一个对象 bean,不是单例对象,所以不会进入三级缓存,也就不能通过三级缓存--->解决循环依赖问题


理解:


因为我们第一次创建的对象是基于单例的,所有单例的对象走完了完整的生命周期--进入缓存--然后才开始可以解决循环依赖


由于 photoType 每次要重新创建一个对象,所以无法放入缓存中,也就不能使用三级缓存来解决循环依赖


写到这里,关于 spring 循环的问题就告一段落,其实还有下篇,集中对于如何对象 bean 实现三级缓存的细节,我们来处理循环依赖,下节我们接着干,希望你会有所收获,我们一起进步,相互成就才是最好的状态。


大家晚安,好梦,我是卢卡,感兴趣就点个赞,再走吧

发布于: 2 小时前阅读数: 6
用户头像

卢卡多多

关注

努力寻找生活答案的旅途者 2020.04.12 加入

公众号:卢卡多多,欢迎一起交流学习

评论

发布
暂无评论
spring的循环依赖