spring怎么发现循环依赖?是如何解决循环依赖的
前言
在面试的时候这两年有一个非常高频的关于spring的问题,那就是spring是如何解决循环依赖的。这个问题听着就是轻描淡写的一句话,其实考察的内容还是非常多的,主要还是考察的应聘者有没有研究过spring的源码。但是说实话,spring的源码其实非常复杂的,研究起来并不是个简单的事情,所以我们此篇文章只是为了解释清楚Spring是如何解决循环依赖的这个问题。
什么是循环依赖?循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
循环依赖有哪几种方式?setter方式 - 单例/** * A服务类 * * @author liudong * @date 2021/12/20 14:48 */@Service("aService")public class AService { @Autowired public BService bService;}
/** * B服务类 * * @author liudong * @date 2021/12/20 14:54 */@Service("bService")public class BService { @Autowired public AService aService;}
/** * 配置类 * * @author liudong * @date 2021/12/20 14:59 */@Configuration@ComponentScanpublic class AppConfig {}
/** * 测试应用类 * * @author liudong * @date 2021/12/20 14:56 */public class TestApplication { public static void main(String[] args) { ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class); AService aService = (AService) annotationConfigApplicationContext.getBean("aService"); System.out.println(aService.bService); BService bService = (BService) annotationConfigApplicationContext.getBean("bService"); System.out.println(bService.aService); }}
执行结果:
com.gaodun.spring.BService@1ed6388acom.gaodun.spring.AService@5a45133e
虽然AService和BService产生了循环依赖的关系,但是没有异常抛出。说明被Spring解决了。但是能够通过“setAllowCircularReferences(false)”来禁用循环引用。这样也会抛出BeanCurrentlyInCreationException异常!测试代码如下:
/** * 测试应用类 * * @author liudong * @date 2021/12/20 14:56 */public class TestApplication { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.setAllowCircularReferences(false); applicationContext.register(AppConfig.class); applicationContext.refresh(); AService aService = (AService) applicationContext.getBean("aService"); System.out.println(aService.bService); BService bService = (BService) applicationContext.getBean("bService"); System.out.println(bService.aService); }}
执行结果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService': Requested bean is currently in creation: Is there an unresolvable circular reference?
需要注意的是,我们不能这么写:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.setAllowCircularReferences(false);
如果你这么写,程序执行完第一行代码,整个Spring容器已经初始化完成了,你再设置不允许循环依赖,也于事无补了。
setter方式 - prototype使用@Scope(BeanDefinition.SCOPE_PROTOTYPE),进行多例的setter注入 ,每次请求都会建立一个实例对象。
/** * A服务类 * * @author liudong * @date 2021/12/20 14:48 */@Service("aService")@Scope(BeanDefinition.SCOPE_PROTOTYPE)public class AService { @Autowired public BService bService;}
/** * B服务类 * * @author liudong * @date 2021/12/20 14:54 */@Service("bService")@Scope(BeanDefinition.SCOPE_PROTOTYPE)public class BService { @Autowired public AService aService;}
执行结果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService': Requested bean is currently in creation: Is there an unresolvable circular reference?
对于“prototype”作用域bean,Spring容器没法完成依赖注入,由于Spring容器不进行缓存“prototype”做用域的bean,所以没法提早暴露一个建立中的bean。
构造器循环依赖/** * A服务类 * * @author liudong * @date 2021/12/20 14:48 */@Service("aService")public class AService { public BService bService; public AService(BService bService) { this.bService = bService; }}
/** * B服务类 * * @author liudong * @date 2021/12/20 14:54 */@Service("bService")public class BService { public AService aService; public BService(AService aService) { this.aService = aService; }}
执行结果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'aService': Requested bean is currently in creation: Is there an unresolvable circular reference?
通过构造器注入构成的循环依赖是没法解决的,由于加入singletonFactories三级缓存的前提是执行了构造器,因此构造器的循环依赖无法解决,只能抛异常BeanCurrentlyInCreationException异常表示循环异常。
Spring是如何解决循环依赖的?根据上文所述,Spring通过setter方式单例注入,是可以解决循环依赖的。关键源码如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { // ... /** *一级缓存:单例池 *存放已经初始化的bean——成品 */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); /** *三级缓存:单例工厂的高速缓存 *存放生成bean的工厂 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); /** *二级缓存:早期单例对象的高速缓存 *存放已经实例化但未初始化(未填充属性)的的bean——半成品 */ private final Map<String, Object> earlySingletonObjects = new HashMap(16); //... /** * *此处就是解决循环依赖的关键,这段代码发生在createBeanInstance以后, *也就是说单例对象此时已经被建立出来的。这个对象已经被生产出来了, *虽然还不完美(尚未进行初始化的第二步和第三步), *可是已经能被人认出来了(根据对象引用能定位到堆中的对象), *因此Spring此时将这个对象提早曝光出来让你们认识,让你们使用。 */ protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized(this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } // ... @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { //首先去一级缓存中获取如果获取的到说明bean已经存在,直接返回 Object singletonObject = this.singletonObjects.get(beanName); //如果一级缓存中不存在,则去判断该bean是否在创建中,如果该bean正在创建中,就说明了,这个时候发生了循环依赖 if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { synchronized(this.singletonObjects) { //如果发生循环依赖,首先去二级缓存中获取,如果获取到则返回,这个地方就是获取aop增强以后的bean singletonObject = this.earlySingletonObjects.get(beanName); //如果二级缓存中不存在,且允许提前访问三级引用 if (singletonObject == null && allowEarlyReference) { //去三级缓存中获取 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { //如果三级缓存中的lambda表达式存在,执行aop,获取增强以后的对象,为了防止重复aop,将三级缓存删除,升级到二级缓存中 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } // ...}
以上便是spring解决循环依赖的核心代码,那么对于:A对象setter依赖B对象,B对象setter依赖A对象,Spring解决循环依赖的详细步骤如下:
- (1) A首先完成了初始化的第一步,而且将本身提早曝光到singletonFactories中。
- (2) 此时进行初始化的第二步,发现本身依赖对象B,此时就尝试去get(B),发现B尚未被create,因此走create流程,B在初始化第一步的时候发现本身依赖了对象A,因而尝试get(A),尝试一级缓存singletonObjects(确定没有,由于A还没初始化彻底),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,因为A经过ObjectFactory将本身提早曝光了,因此B可以经过ObjectFactory.getObject拿到A对象(虽然A尚未初始化彻底,可是总比没有好呀),B拿到A对象后顺利完成了初始化阶段一、二。彻底初始化以后将本身放入到一级缓存singletonObjects中。
- (3) 此时返回A中,A此时能拿到B的对象顺利完成本身的初始化阶段二、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,并且更加幸运的是,因为B拿到了A的对象引用,因此B如今hold住的A对象完成了初始化。
- 03-20荣耀note10支持视频输出:教你一招荣耀Note10一秒变电脑
- 11-27运动品牌未来3到5年最具前景 运动品牌这一年市场饱和
- 03-23搞笑表情包穷困潦倒,搞笑表情包,身无分文两手空空
- 11-08王鹤棣道明寺又拽又帅:王鹤棣被封最帅道明寺
- 01-20重庆定向选调重庆大学 重庆两区县一高校公招91人
- 04-01声音怎么算好听?声音好听是有怎样的魔力
- 02-20柿子尽量不与螃蟹同吃,柿子尽量不与螃蟹同吃
- 02-14官池春雁二首诗文:邹小泓诗词与
- 09-21湖南联通沃家庭套餐4g:湖南联通发布,沃4G
- 01-25播求vs一龙二番战 播求一龙三番战确定四年恩怨一战了结
- 12-03psv黑商店1.08更新了什么游戏?港服PSV游戏黑五促销开始
- 03-05赣深高铁经过信丰吗?读图,镜头里的赣深高铁
- 02-29六年级上册语文七律长征教材全解:人教版小学六年级语文上册课文解读
- 11-20婚后房子加上配偶名字:婚后房子加了配偶名真的一人一半吗
- 04-27商丘高铁新城安置房项目情况 商丘高铁新城A5项目19号楼封顶
- 10-10卡农简单指弹教学 这位58岁大叔的轮指实在是绝了
热门
推荐
- 1小学语文教师年度考核个人总结范文精选五篇441
- 2孕妇梦见柿子489
- 3关于梦想的英语小短文阅读228
- 4大学生助学金申请书分享8篇298
- 5公司总经理元宵节联欢会贺词479
- 6练习瑜伽球有什么相关好处471
- 7用电设备改造工程合同样书170
- 8有关环保环境的作文358