女朋友都能看懂,Spring如何解决循环依赖?
介绍
先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成
Spring的循环依赖有两种场景
构造器的循环依赖
属性的循环依赖
构造器的循环依赖,可以在构造函数中使用@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部分
bean的实例化过程,即调用构造函数将对象创建出来
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循环依赖的文章都会提到,这次你明白工作原理是什么了把
总结一波
拿bean的时候先从singletonObjects(一级缓存)中获取
如果获取不到,并且对象正在创建中,就从earlySingletonObjects(二级缓存)中获取
如果还是获取不到就从singletonFactories(三级缓存)中获取,然后将获取到的对象放到earlySingletonObjects(二级缓存)中,并且将bean对应的singletonFactories(三级缓存)清除
bean初始化完毕,放到singletonObjects(一级缓存)中,将bean对应的earlySingletonObjects(二级缓存)清除
最后,再附上我历时三个月总结的 Java 面试 + Java 后端技术学习指南,笔者这几年及春招的总结,github 1.1k star,拿去不谢!下载方式1. 首先扫描下方二维码
2. 后台回复「Java面试」即可获取
女朋友都能看懂,Spring如何解决循环依赖?相关推荐
- 人人都能看懂的Spring源码解析,Spring如何解决循环依赖
人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...
- 小学生都能看懂,彻底解决环境搭建难题,一步一截图,再无VMware网络难题
小学生都能看懂,彻底解决环境搭建难题,一步一截图,再无VMware网络难题 原创 韦东山 百问科技 1周前 上周四我们预告了这周要发布环境搭建的终极解决方案,经过一周的努力,终于写好了文档,Ubunt ...
- Spring 如何解决循环依赖问题
在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...
- 高频面试题:Spring 如何解决循环依赖?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 在关于Spring的面试中,我们经常会被问到一个问题:Spring ...
- 面试问你Spring如何解决循环依赖的时候,不要一脸懵逼了!
点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 来源:http://h5ip.cn/AXHC 在关于Spring的面试中,我们经常会被问到一个 ...
- 闷棍暴打面试官 Spring源码系列: (一) Spring 如何解决循环依赖
前言 初夏时节, 时间: AM 7.30分左右, 空无一人的健身房里,一个硕大的身体在跑步机上扭动着, 不一会头上便挥汗如雨, 他嘴上还不时嘀咕着 "循环依赖,单例模式,Bean的定位加载注 ...
- Spring系列五:Spring怎么解决循环依赖
15.说说循环依赖? 什么是循环依赖? Spring循环依赖 Spring 循环依赖:简单说就是自己依赖自己,或者和别的Bean相互依赖. 鸡和蛋 只有单例的Bean才存在循环依赖的情况,原型(Pro ...
- Spring 如何解决循环依赖?
在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖的问题的. 本文主要针对这个问题,从源码的角度对其实现原理进行讲解. 1. 过程演示 关于Spring bean的创 ...
- Spring如何解决循环依赖问题
一.循环依赖问题全景图 二.什么是循环依赖问题? 1.什么是循环依赖: 类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生. 比如下图中A类依赖了B类,B类依赖了C类,而最后C类又依赖了A类, ...
最新文章
- CDH 的Cloudera Manager免费与收费版的对比表
- hexo部署至FTP-COS
- Ubuntu 12.10使用apt安装Oracle/Sun JDK
- windows 2008 r2 AD域控服务器部署
- 表格是html文档的基本属性吗,Html
- java测试网址_支付宝:电脑网站沙箱测试(Java)
- java匿名类 - new接口
- python对象的相关术语
- 读《三体》差点污了我的三观
- elasticsearch怎么实现拼音首字母查询
- 爱情:溺水三千只取一瓢饮
- 矩阵和向量的范式(Norms for Vectors and Matrices)
- U盘重装系统win7_U盘安装win7教程
- 【车载】【ADC】通俗易懂ADC
- Windows下安装Golang开发环境-SDK安装
- 什么是5G SAR测试,FCC/CE中5G Sub-6GHz与5G 毫米波测试,5G毫米波测试
- DPDK 编译安装(meson ninja)
- 3. list 方法
- [转载学习] 背包问题九讲
- 洛谷P2404 自然数的拆分问题