面试题:spring的循环依赖问题以及如何解决

文章目录

  • 什么是循环依赖?
  • 循环依赖的产生
    • 1 构造器方式产生的循环依赖(spring本身无法解决,抛出异常)
    • 2 setter方法产生的循环依赖
    • 3 setter方式原型,prototype
  • spring用三级缓存解决循环依赖问题:
  • 总结

什么是循环依赖?

循环依赖就是循环引用 ,指两个或者多个bean互相持有依赖对方,比如A引用B, B引用C,C引用A,最终形成一个闭环
面试题:spring的循环依赖问题以及如何解决_第1张图片

补充: 注意区别于循环调用;

什么是循环调用?
指方法之间的环调用,循环调用是无解的,除非有终结条件,否则就是死循环,最终会导致内存溢出异常

循环依赖的产生

1 构造器方式产生的循环依赖(spring本身无法解决,抛出异常)

表示通过构造器注入造成的循环依赖,此依赖是无解的,强行依赖只能抛出异常BeanCreationException;

bean的创建过程:
spring容器将每一个正在创建的bean标识符放在一个当前创建的bean池中,bean标识符在创建过程中将一直保持在这个池中,因此在创建bean的过程中如果发现自己已经在池中,则抛出BeanCreationException异常表示循环依赖,而对于创建完成的bean将从 当前创建bean池中清除掉;

出现依赖错误的原因:
Spring容器先创建单例A,A依赖B,然后将A放在“当前创建Bean池”中,此时创建B, B依赖C,然后将B放在“当前创建Bean池”中,此时创建C,C又依赖A, 但是,此时A已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

2 setter方法产生的循环依赖

指通过setter注入方式构成的循环依赖。

spring实例化bean流程图:
面试题:spring的循环依赖问题以及如何解决_第2张图片
spring的解决办法:
Spring先用构造器实例化Bean对象----->将实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象的引用方法。结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,就不会出来循环的问题。

3 setter方式原型,prototype

错误原因:
scope=“prototype” 意思是 每次请求都会创建一个实例对象。

两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。

对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring

容器不进行缓存,因此无法提前暴露一个创建中的Bean。

spring用三级缓存解决循环依赖问题:

三级缓存如下:

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

名词解释:
singletonObjects :指单例对象的cache
singletonFactories: 指单例对象工厂的cache
earlySingletonObjects:指提前曝光的单例对象的cache

举例解释三级缓存的实际应用:

如A依赖B,B依赖C的场景:

A完成初始化第一步,并且将自己提前曝光到singletonFactories中,
此时进行初始化的第二步,发现自己依赖对象B,就去get(B),发现B还没有被初始化,所以走初始化流
程,
B在初始化第一步发现自己依赖于A,于是尝试get(A),
尝试一级缓存singletonObjects(A还没有完全初始化),
尝试二级缓存earlySingletonObjects(也没有),
尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能通过ObjectFactory.getObject拿到A对象(尚未完全初始化的状态),
B拿到A对象后顺利完成了初始化阶段1,2,3,完全初始化之后将自己放入到一级缓存singleObjects中,
此时返回A,A此时能拿到B的对象顺利完成自己的初始化阶段2,3,最终A也完成了自己的初始化阶段,放入到一级缓存singletonObjects中;

总结

Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引用原理,比较完美地解决了某些情况下的循环依赖问题!

参考1
参考2

你可能感兴趣的:(面试总结,spring循环依赖)