Spring如何解决循环依赖问题

Spring如何解决循环依赖问题

一、循环依赖问题全景图

Spring如何解决循环依赖问题

 

二、什么是循环依赖问题?

1、什么是循环依赖:

类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。

比如下图中A类依赖了B类,B类依赖了C类,而最后C类又依赖了A类,这样就形成了循环依赖问题。

Spring如何解决循环依赖问题

2、循环依赖问题案例分析:

(1)演示代码:

public class ClassA {
	private ClassB classB;

	public ClassB getClassB() {
		return classB;
	}

	public void setClassB(ClassB classB) {
		this.classB = classB;
	}
}
public class ClassB {
	private ClassA classA;

	public ClassA getClassA() {
		return classA;
	}

	public void setClassA(ClassA classA) {
		this.classA = classA;
	}
}

(2)配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="classA" class="ioc.cd.ClassA">
		<property name="classB" ref="classB"></property>
	</bean>
	<bean id="classB" class="ioc.cd.ClassB">
		<property name="classA" ref="classA"></property>
	</bean>
</beans>

(3)测试代码:

	@Test
	public void test() throws Exception {
		// 创建IoC容器,并进行初始化
		String resource = "spring/spring-ioc-circular-dependency.xml";
		ApplicationContext context = new ClassPathXmlApplicationContext(resource);
		// 获取ClassA的实例(此时会发生循环依赖)
		ClassA classA = (ClassA) context.getBean(ClassA.class);
	}

3、通过Spring IOC流程的源码分析循环依赖问题:

Spring如何解决循环依赖问题

 

三、循环依赖问题的类型

循环依赖问题在Spring中主要有三种情况:

  • (1)通过构造方法进行依赖注入时产生的循环依赖问题。
  • (2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
  • (3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。其实也很好解释:

  • 第(1)种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
  • 第(2)种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。

 

四、如何解决循环依赖问题?

1、Spring解决的单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过两个缓存来解决的,请看下图:

Spring如何解决循环依赖问题

 

五、Spring三大缓存介绍

Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存 ==> 二级缓存 ==> 三级缓存。

Spring如何解决循环依赖问题

1、一级缓存:Map<String, Object> singletonObjects:

(1)第一级缓存的作用:

  • 用于存储单例模式下创建的Bean实例(已经创建完毕)。
  • 该缓存是对外使用的,指的就是使用Spring框架的程序员。

(2)存储什么数据?

  • K:bean的名称
  • V:bean的实例对象(有代理对象则指的是代理对象,已经创建完毕)

2、第二级缓存:Map<String, Object> earlySingletonObjects:

(1)第二级缓存的作用:

  • 用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)。
  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
  • 为了解决第一个classA引用最终如何替换为代理对象的问题(如果有代理对象)

3、第三级缓存:Map<String, ObjectFactory<?>> singletonFactories:

(1)第三级缓存的作用:

  • 通过ObjectFactory对象来存储单例模式下提前暴露的Bean实例的引用(正在创建中)。
  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
  • 此缓存是解决循环依赖最大的功臣

(2)存储什么数据?

  • K:bean的名称
  • V:ObjectFactory,该对象持有提前暴露的bean的引用

Spring如何解决循环依赖问题

(3)为什么第三级缓存要使用ObjectFactory?

如果仅仅是解决循环依赖问题,使用二级缓存就可以了,但是如果对象实现了AOP,那么注入到其他bean的时候,并不是最终的代理对象,而是原始的。这时就需要通过三级缓存的ObjectFactory才能提前产生最终的需要代理的对象。

Spring如何解决循环依赖问题

(4)什么时候将Bean的引用提前暴露给第三级缓存的ObjectFactory持有?时机就是在第一步实例化之后,第二步依赖注入之前,完成此操作。

Spring如何解决循环依赖问题

 

六、解决构造函数相互注入造成的循环依赖:

前面说Spring可以自动解决单例模式下通过setter()方法进行依赖注入产生的循环依赖问题。而对于通过构造方法进行依赖注入时产生的循环依赖问题没办法自动解决,那针对这种情况,我们可以使用@Lazy注解来解决。

也就是说,对于类A和类B都是通过构造器注入的情况,可以在A或者B的构造函数的形参上加个@Lazy注解实现延迟加载。@Lazy实现原理是,当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。

比如,类A的创建:A a=new A(B),需要依赖对象B,发现构造函数的形参上有@Lazy注解,那么就不直接创建B了,而是使用动态代理创建了一个代理类B1,此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A。但因为在注入依赖时,类A并没有完全的初始化完,实际上注入的是一个代理对象,只有当他首次被使用的时候才会被完全的初始化。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/100035.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • PrintWriter作用[通俗易懂]

    PrintWriter作用[通俗易懂]PrintWriter()的作用是为了定义流输出的位置,并且此流可以正常的存储中文,减少乱码输出。PrintWriter可以在写入同时对写入的数据进行格式化。PrintStream主要操作byte流,而PrintWriter用来操作字符流…

    2022年8月10日
    10
  • java最长递增子序列_JAVA最长递增子序列「建议收藏」

    java最长递增子序列_JAVA最长递增子序列「建议收藏」问题描述LIS(LongestIncreasingSubsequence,最长递增子序列):给出一个序列a1,a2,a3,a4,a5,a6,a7…an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1最长递增子序列实例分析17359481最长递增子序列算法设计设b[i]是在a[i]为单调递增子序列最后一个元素时,所得最长单调递增…

    2022年6月11日
    30
  • .NET Core 的Generic Host 之Generic Host Builder

    .NET Core 的Generic Host 之Generic Host Builder通用Host(GenericHost)与webHost不同的地方就是通用Host解耦了Http请求管道,使得通用Host拥有更广的应用场景。比如:消息收发、后台…

    2022年10月12日
    3
  • 如何保证缓存与数据库的双写一致性?

    作者 | 你是我的海啸 来源 | https://blog.csdn.net/chang384915878 分布式缓存是现在很多分布式应用中必不可少的组件,但是…

    2021年6月22日
    110
  • eclipse SVN插件的缓存清理[通俗易懂]

    eclipse SVN插件的缓存清理[通俗易懂]工具原料:SVN客户端;windowxp;eclipse中的缓存清理主要有: eclipse清理网页缓存; eclipse清理XSD文件缓存; eclipse清理svn账号缓存; 情况一:eclipse清理网页缓存。修改了代码多次刷新页面[已经清除过浏览器缓存]后页面调试仍显示源代码解决步骤:①停止tomcat的运行;②在eclipse中的Servers下找到并选中tomcat,右键选择”clean…”;③重新启动tomcat,刷新页面;④设置b

    2022年10月14日
    4
  • 语义分割 实例分割 全景分割_语义分割应用场景

    语义分割 实例分割 全景分割_语义分割应用场景之前看过一篇使用分割思想进行目标检测,所以这里补习下一些分割相关的基础知识。这里重点说下语义分割、实力分割和全景分割的区别。1、semanticsegmentation(语义分割)通常意义上的目标分割指的就是语义分割,图像语义分割,简而言之就是对一张图片上的所有像素点进行分类语义分割(下图左)就是需要区分到图中每一点像素点,而不仅仅是矩形框框住了。但是同一物体的不同实例不需要单独分…

    2022年8月21日
    7

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号