spring循环依赖

122 阅读8分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

晚上好,我是卢卡,对于很多新手,项目中刚开始使用的框架就是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,两个对象AB,互相依赖

对象A:

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

/**

  • @author lucas

  • @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;

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 Lucas

  • @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 Lucas
  • @description spring单例容器化实现循环依赖*/public class ClientSpringcontainer {public static void main(String[] args) {
  • }}
  • 注意事项:
  • 第一次开始的时候,可能会遇见bean不存在,注意建立时候配置文件的位置,检查A和B的位置,我准备了链接,给你谈谈路,
  • 如何优雅的解决:

winterchen.blog.csdn.net/article/det…

思考一下,是因为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实现三级缓存的细节,我们来处理循环依赖,下节我们接着干,希望你会有所收获,我们一起进步,相互成就才是最好的状态。

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