“这是我参与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,两个对象A和B,互相依赖
对象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实现三级缓存的细节,我们来处理循环依赖,下节我们接着干,希望你会有所收获,我们一起进步,相互成就才是最好的状态。
大家晚安,好梦,我是卢卡,感兴趣就点个赞,再走吧