介绍

先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成

Spring的循环依赖有两种场景

  1. 构造器的循环依赖

  2. 属性的循环依赖

构造器的循环依赖,可以在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入

属性的循环依赖主要是通过3个map来解决的

构造器的循环依赖

@Component
public class ConstructorA {private ConstructorB constructorB;@Autowiredpublic ConstructorA(ConstructorB constructorB) {this.constructorB = constructorB;}
}
@Component
public class ConstructorB {private ConstructorA constructorA;@Autowiredpublic ConstructorB(ConstructorA constructorA) {this.constructorA = constructorA;}
}
@Configuration
@ComponentScan("com.javashitang.dependency.constructor")
public class ConstructorConfig {
}
public class ConstructorMain {public static void main(String[] args) {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(ConstructorConfig.class);System.out.println(context.getBean(ConstructorA.class));System.out.println(context.getBean(ConstructorB.class));}
}

运行ConstructorMain的main方法的时候会在第一行就报异常,说明Spring没办法初始化所有的Bean,即上面这种形式的循环依赖Spring无法解决。

附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,笔者这几年及春招的总结,github 1.1k star,拿去不谢!下载方式1. 首先扫描下方二维码
2. 后台回复「Java面试」即可获取

我们可以在ConstructorA或者ConstructorB构造函数的参数上加上@Lazy注解就可以解决

@Autowired
public ConstructorB(@Lazy ConstructorA constructorA) {this.constructorA = constructorA;
}

因为我们主要关注属性的循环依赖,构造器的循环依赖就不做过多分析了

属性的循环依赖

先演示一下什么是属性的循环依赖

@Component
public class FieldA {@Autowiredprivate FieldB fieldB;
}
@Component
public class FieldB {@Autowiredprivate FieldA fieldA;
}
@Configuration
@ComponentScan("com.javashitang.dependency.field")
public class FieldConfig {
}
public class FieldMain {public static void main(String[] args) {AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(FieldConfig.class);// com.javashitang.dependency.field.FieldA@3aa9e816System.out.println(context.getBean(FieldA.class));// com.javashitang.dependency.field.FieldB@17d99928System.out.println(context.getBean(FieldB.class));}
}

Spring容器正常启动,能获取到FieldA和FieldB这2个Bean

属性的循环依赖在面试中还是经常被问到的。总体来说也不复杂,但是涉及到Spring Bean的初始化过程,所以感觉比较复杂,我写个demo演示一下整个过程

Spring的Bean的初始化过程其实比较复杂,为了方便理解Demo,我就把Spring Bean的初始化过程分为2部分

  1. bean的实例化过程,即调用构造函数将对象创建出来

  2. bean的初始化过程,即填充bean的各种属性

bean初始化过程完毕,则bean就能被正常创建出来了

下面开始写Demo,ObjectFactory接口用来生产Bean,和Spring中定义的接口一样

public interface ObjectFactory<T> {T getObject();
}
public class DependencyDemo {// 初始化完毕的Beanprivate final Map<String, Object> singletonObjects =new ConcurrentHashMap<>(256);// 正在初始化的Bean对应的工厂,此时对象已经被实例化private final Map<String, ObjectFactory<?>> singletonFactories =new HashMap<>(16);// 存放正在初始化的Bean,对象还没有被实例化之前就放进来了private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));public  <T> T getBean(Class<T> beanClass) throws Exception {// 类名为Bean的名字String beanName = beanClass.getSimpleName();// 已经初始化好了,或者正在初始化Object initObj = getSingleton(beanName, true);if (initObj != null) {return (T) initObj;}// bean正在被初始化singletonsCurrentlyInCreation.add(beanName);// 实例化beanObject object = beanClass.getDeclaredConstructor().newInstance();singletonFactories.put(beanName, () -> {return object;});// 开始初始化bean,即填充属性Field[] fields = object.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);// 获取需要注入字段的classClass<?> fieldClass = field.getType();field.set(object, getBean(fieldClass));}// 初始化完毕singletonObjects.put(beanName, object);singletonsCurrentlyInCreation.remove(beanName);return (T) object;}/*** allowEarlyReference参数的含义是Spring是否允许循环依赖,默认为true* 所以当allowEarlyReference设置为false的时候,当项目存在循环依赖,会启动失败*/public Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();}}}}return singletonObject;}/*** 判断bean是否正在被初始化*/public boolean isSingletonCurrentlyInCreation(String beanName) {return this.singletonsCurrentlyInCreation.contains(beanName);}}

测试一波

public static void main(String[] args) throws Exception {DependencyDemo dependencyDemo = new DependencyDemo();// 假装扫描出来的对象Class[] classes = {A.class, B.class};// 假装项目初始化所有beanfor (Class aClass : classes) {dependencyDemo.getBean(aClass);}// trueSystem.out.println(dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class));// trueSystem.out.println(dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class));
}

是不是很简单?我们只用了2个map就搞定了Spring的循环依赖

2个Map就能搞定循环依赖,那为什么Spring要用3个Map呢?

原因其实也很简单,当我们从singletonFactories中根据BeanName获取相应的ObjectFactory,然后调用getObject()这个方法返回对应的Bean。在我们的例子中 ObjectFactory的实现很简单哈,就是将实例化好的对象直接返回,但是在Spring中就没有这么简单了,执行过程比较复杂,为了避免每次拿到ObjectFactory然后调用getObject(),我们直接把ObjectFactory创建的对象缓存起来不就行了,这样就能提高效率了

比如A依赖B和C,B和C又依赖A,如果不做缓存那么初始化B和C都会调用A对应的ObjectFactory的getObject()方法。如果做缓存只需要B或者C调用一次即可。

知道了思路,我们把上面的代码改一波,加个缓存。

public class DependencyDemo {// 初始化完毕的Beanprivate final Map<String, Object> singletonObjects =new ConcurrentHashMap<>(256);// 正在初始化的Bean对应的工厂,此时对象已经被实例化private final Map<String, ObjectFactory<?>> singletonFactories =new HashMap<>(16);// 缓存Bean对应的工厂生产好的Beanprivate final Map<String, Object> earlySingletonObjects =new HashMap<>(16);// 存放正在初始化的Bean,对象还没有被实例化之前就放进来了private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));public  <T> T getBean(Class<T> beanClass) throws Exception {// 类名为Bean的名字String beanName = beanClass.getSimpleName();// 已经初始化好了,或者正在初始化Object initObj = getSingleton(beanName, true);if (initObj != null) {return (T) initObj;}// bean正在被初始化singletonsCurrentlyInCreation.add(beanName);// 实例化beanObject object = beanClass.getDeclaredConstructor().newInstance();singletonFactories.put(beanName, () -> {return object;});// 开始初始化bean,即填充属性Field[] fields = object.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);// 获取需要注入字段的classClass<?> fieldClass = field.getType();field.set(object, getBean(fieldClass));}singletonObjects.put(beanName, object);singletonsCurrentlyInCreation.remove(beanName);earlySingletonObjects.remove(beanName);return (T) object;}/*** allowEarlyReference参数的含义是Spring是否允许循环依赖,默认为true*/public Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null&& isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory =this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;}
}

我们写的getSingleton的实现和org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)的实现一模一样,这个方法几乎所有分析Spring循环依赖的文章都会提到,这次你明白工作原理是什么了把

总结一波

  1. 拿bean的时候先从singletonObjects(一级缓存)中获取

  2. 如果获取不到,并且对象正在创建中,就从earlySingletonObjects(二级缓存)中获取

  3. 如果还是获取不到就从singletonFactories(三级缓存)中获取,然后将获取到的对象放到earlySingletonObjects(二级缓存)中,并且将bean对应的singletonFactories(三级缓存)清除

  4. bean初始化完毕,放到singletonObjects(一级缓存)中,将bean对应的earlySingletonObjects(二级缓存)清除

最后,再附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,笔者这几年及春招的总结,github 1.1k star,拿去不谢!下载方式1. 首先扫描下方二维码
2. 后台回复「Java面试」即可获取

女朋友都能看懂,Spring如何解决循环依赖?相关推荐

  1. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

  2. 小学生都能看懂,彻底解决环境搭建难题,一步一截图,再无VMware网络难题

    小学生都能看懂,彻底解决环境搭建难题,一步一截图,再无VMware网络难题 原创 韦东山 百问科技 1周前 上周四我们预告了这周要发布环境搭建的终极解决方案,经过一周的努力,终于写好了文档,Ubunt ...

  3. Spring 如何解决循环依赖问题

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...

  4. 高频面试题:Spring 如何解决循环依赖?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 在关于Spring的面试中,我们经常会被问到一个问题:Spring ...

  5. 面试问你Spring如何解决循环依赖的时候,不要一脸懵逼了!

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 来源:http://h5ip.cn/AXHC 在关于Spring的面试中,我们经常会被问到一个 ...

  6. 闷棍暴打面试官 Spring源码系列: (一) Spring 如何解决循环依赖

    前言 初夏时节, 时间: AM 7.30分左右, 空无一人的健身房里,一个硕大的身体在跑步机上扭动着, 不一会头上便挥汗如雨, 他嘴上还不时嘀咕着 "循环依赖,单例模式,Bean的定位加载注 ...

  7. Spring系列五:Spring怎么解决循环依赖

    15.说说循环依赖? 什么是循环依赖? Spring循环依赖 Spring 循环依赖:简单说就是自己依赖自己,或者和别的Bean相互依赖. 鸡和蛋 只有单例的Bean才存在循环依赖的情况,原型(Pro ...

  8. Spring 如何解决循环依赖?

    在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖的问题的. 本文主要针对这个问题,从源码的角度对其实现原理进行讲解. 1. 过程演示 关于Spring bean的创 ...

  9. Spring如何解决循环依赖问题

    一.循环依赖问题全景图 二.什么是循环依赖问题? 1.什么是循环依赖: 类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生. 比如下图中A类依赖了B类,B类依赖了C类,而最后C类又依赖了A类, ...

最新文章

  1. CDH 的Cloudera Manager免费与收费版的对比表
  2. hexo部署至FTP-COS
  3. Ubuntu 12.10使用apt安装Oracle/Sun JDK
  4. windows 2008 r2 AD域控服务器部署
  5. 表格是html文档的基本属性吗,Html
  6. java测试网址_支付宝:电脑网站沙箱测试(Java)
  7. java匿名类 - new接口
  8. python对象的相关术语
  9. 读《三体》差点污了我的三观
  10. elasticsearch怎么实现拼音首字母查询
  11. 爱情:溺水三千只取一瓢饮
  12. 矩阵和向量的范式(Norms for Vectors and Matrices)
  13. U盘重装系统win7_U盘安装win7教程
  14. 【车载】【ADC】通俗易懂ADC
  15. Windows下安装Golang开发环境-SDK安装
  16. 什么是5G SAR测试,FCC/CE中5G Sub-6GHz与5G 毫米波测试,5G毫米波测试
  17. DPDK 编译安装(meson ninja)
  18. 3. list 方法
  19. [转载学习] 背包问题九讲
  20. 洛谷P2404 自然数的拆分问题

热门文章

  1. 【Hello CC.NET】巧用模板简化配置
  2. V4L2 driver(一). 整体框架
  3. 内核request_mem_region 和 ioremap的理解
  4. tessorflow基本语法
  5. 文本挖掘预处理流程总结(2)— 英文
  6. cmake (4)引用子目录的库
  7. 05-Exception Handling Framework
  8. 人工智能-基于U^2-Net的肖像画生成算法
  9. python2.7获取当前脚本目录
  10. Gosper 的序列 循环检测