精选Spring面试题:如何解决循环依赖问题

开课吧灭霸詹2021-02-19 11:01

点赞
有用
分享分享

在面试时,您有没有遇到过,面试官问的问题时候,自己答不上来,简历通过了,也有面试机会,但是面试时的状态和结果却不如人意。面试时自己准备的不充分,自己知识积累的不够多,就是面试时的短板,所以想要给面试官留下好的印象,那就要不断的去丰富自我的知识以及能力。为了大家更好的面对面试,今天小编就为大家介绍Spring高频面试题:如何解决循环依赖问题。

循环依赖问题全景图

Java

什么是循环依赖问题?

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

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

Java

循环依赖问题案例分析

演示代码:

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;

}

}

配置文件:

<?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>

测试代码:

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

}

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

Java

以上案例有几种循环依赖问题?

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

通过构造方法进行依赖注入时产生的循环依赖问题。

通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。

通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

注意:在Spring中,只有【第三种方式】的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。

其实也很好解释:

第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题

第二种setter方法&&多例的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。

如何解决循环依赖问题?

那Spring到底是如何解决的setter方法依赖注入引起的循环依赖问题呢?请看下图(其实主要是通过两个缓存来解决的):

Java

Spring三大缓存介绍

Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。

如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。读取顺序依次是一级缓存-->二级缓存-->三级缓存

Java

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

第一级缓存的作用?

用于存储单例模式下创建的Bean实例(已经创建完毕)。

该缓存是对外使用的,指的就是使用Spring框架的程序员。

存储什么数据?

K:bean的名称

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

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

第二级缓存的作用?

用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)。

该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。

为了解决第一个classA引用最终如何替换为代理对象的问题(如果有代理对象)请爬楼参考演示案例

存储什么数据?

K:bean的名称

V:bean的实例对象(有代理对象则指的是代理对象,该Bean还在创建中)

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

第三级缓存的作用?

通过ObjectFactory对象来存储单例模式下提前暴露的Bean实例的引用(正在创建中)。

该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。

此缓存是解决循环依赖最大的功臣

存储什么数据?

K:bean的名称

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

Java

为什么第三级缓存要使用ObjectFactory?需要提前产生代理对象。

Java

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

Java

总结

以上就是Spring解决循环依赖的关键点!总结来说,就是要搞清楚以下几点:

搞清楚Spring三级缓存的作用?

搞清楚第三级缓存中ObjectFactory的作用?

搞清楚为什么需要第二级缓存?

搞清楚什么时候使用三级缓存(添加和查询操作)?

搞清楚什么时候使用二级缓存(添加和查询操作)?

当目标对象产生代理对象时,Spring容器中(第一级缓存)到底存储的是谁?

除了以上这些内容,如果您觉得文字内容理解的不够透彻的话,今天小编给您带来Spring常见设计模式剖析视频课程,该课程全程免费,让你理论与实践相结合的快速提升。点击下方图片免费领取课程呀!

Java

生动的视频讲解,可能相比枯燥的文字更能让大家接受,好的选择,成就优秀的自己。

有用
分享