29

首页新闻博问专区闪存班级我的博客我的园子账号设置退出登录注册登录SmileNicky的博客 博客园首页新随笔联系订阅管理随笔 - 283  文章 - 0  评论 - 26 Spring5.0源码学习系列之浅谈循环依赖问题 前言介绍附录:Spring源码学习专栏在上一章的学习中,我们对Bean的创建有了一个粗略的了解,接着本文浅谈Spring循环依赖问题,这是一个面试比较常见的问题1、什么是循环依赖?所谓的循环依赖就是两个以及两个以上的类互相调用依赖,形成闭环// 类A依赖于B
class A{
public B b;
}

// 类B依赖了C
class B{
public C c;
}

// 类C依赖了A
class C{
public A a;
}

然后?看起来是很正常的,我们随便new一个类,循环依赖的类都是能正常调用的A a = new A();
System.out.println(a);
为什么?因为这种情况,A.java->A.class,我们new就能获取到实例的对象,这个通过jvm支持的,jdk是能支持这种情况的,不过本文不详细说明,本文要讨论的Spring中的bean,循环依赖在Spring中就是一个问题了为什么?首先回顾一下之前的知识点,首先在Spring框架中类的创建都是给Spring IOC容器创建的,如图:

然后?真的出现这种情况,会怎么样?

Spring框架检测到这种场景会抛 BeanCurrentlyInCreationException,提前暴露对象的方法,因为Spring创建bean的过程是一个很复杂的过程,首先是xml解析为document对象,document对象再转成BeanDefinition,然后进行bean的生命周期,才算得上是一个真正的spring bean,接着进行后置处理器加工,假如出现这种,设想一下会怎么样?spring容器就会一直循环调用,当然是在特定的条件,为什么说是特定情况?请看下文2、实验环境准备实验环境:SpringFramework版本 Springframework5.0.x开发环境 JAR管理:gradle 4.9/ Maven3.+开发IDE:IntelliJ IDEA 2018.2.5JDK:jdk1.8.0_31Git Server:Git fro window 2.8.3Git Client:SmartGit18.1.5(可选)3、循环依赖问题我们可以通过例子进行验证,创建类A:package com.example.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**

  •  A class
    
  • @author mazq
  • 修改记录
  • 修改后版本: 修改人: 修改日期: 2020/11/05 10:31 修改内容:

*/
@Component
public class A {

//@Autowired
B b;

public A() {
	b = new B();
    System.out.println("A class is create");
}

}

类B:package com.example.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**

  •  B class
    
  • @author mazq
  • 修改记录
  • 修改后版本: 修改人: 修改日期: 2020/11/16 14:03 修改内容:

*/
@Component
public class B {

//@Autowired
A a;

public B() {
	a = new A();
    System.out.println("B class is create");
}

}

注册类A、Bpackage com.example.config;

import com.example.bean.B;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.bean.A;

/**

  •  AppConfiguration
    
  • @author mazq
  • 修改记录
  • 修改后版本: 修改人: 修改日期: 2020/11/05 10:26 修改内容:

*/
@Configuration
public class AppConfiguration {

@Bean
public A a(){
    return new A();
}

@Bean
public B b() {
    return new B();
}

}

package com.example;

import com.example.config.AppConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.bean.A;

/**

  •  TestController
    
  • @author mazq
  • 修改记录
  • 修改后版本: 修改人: 修改日期: 2020/11/05 10:22 修改内容:

*/
public class TestApplication {

public static void testCircularReferences() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(AppConfiguration.class);
    //context.setAllowCircularReferences(false);
    context.refresh();
    A bean = context.getBean(A.class);
    System.out.println(bean);
}

public static void main(String[] args) {
    // 测试Sprin循环依赖
    testCircularReferences();
}

}
经过测试,一直在循环调用:4、循环依赖解决方法对于这种情况,Spring有处理方法?答案是有的,方法就是通过@Autowired注解,当然bean要是单例的,多例的情况不支持,原因后面分析@Component
public class A {

@Autowired
B b;

public A() {
    System.out.println("A class is create");
}

}

补充:除了@Autowired方法,我们还可以通过set方法处理循环依赖问题,当然也是仅支持单例bean,多例的情况不支持5、关闭Spring循环依赖有个疑问?Spring的循环依赖支持,默认情况是开启?是否有什么开关控制?通过源码学习,可以通过setAllowCircularReferences设置AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfiguration.class);
// 关闭Spring循环依赖支持
context.setAllowCircularReferences(false);
context.refresh();
通过测试,设置不开启这个属性的时候,即使加上@Autowired,代码还是抛异常了6、prototype(多例)循环依赖在多例的情况,Spring能支持循环依赖?加上@Scope(“prototype”),将bean变成多例的

经过测试:多例的情况会抛出异常,即使加上了@Autowired,原因请看下文7、Spring循环依赖特征ok,经过前面例子的验证,到这来,可以对Spring的循环依赖特点进行归纳:Spring中的循环依赖场景 构造器的循环依赖,通过构造函数Field属性的循环依赖,通过set方法Spring的循环依赖是默认开启的(setAllowCircularReferences)Spring对单例和多例Bean的支持 单例Bean(singleton) :只能通过@Autowired和set方法支持多例Bean(prototype):默认不支持,直接抛异常BeanCurrentlyInCreationException8、Spring循环依赖原理我们通过实验进行了验证,也归纳出了Spring循环依赖的特点,然后具体原因是什么?我们只能通过源码学习得到答案在上一章的学习中,我们对Bean的创建有了一个粗略的了解,所以,顺着这条路线,跟下源码:在前面的学习,我们知道了{@link org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean}这个方法就是Spring Bean创建的真正执行方法protected T doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 处理BeanName,前面说的FactoryBean带‘&’符号,要在这里进行转换
String beanName = transformedBeanName(name);
Object bean;

// Eagerly check singleton cache for manually registered singletons.
// 从map(singletonObjects)里获取单例bean,确定是否已经存在对应实例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
	if (logger.isDebugEnabled()) {
		if (isSingletonCurrentlyInCreation(beanName)) {
			logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
					"' that is not fully initialized yet - a consequence of a circular reference");
		}
		else {
			logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
		}
	}
	// 两种情况:普通的bean,直接从singletonObjects返回sharedInstance
	//如果是FactoryBean,返回其创建的对象实例
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

else {
	// Fail if we're already creating this bean instance:
	// We're assumably within a circular reference.
	// 校验是否是多例(Prototype)的Bean,多例的bean是不支持循环依赖的
	// 为了避免循环依赖,遇到这种情况,直接抛出异常
	if (isPrototypeCurrentlyInCreation(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName);
	}

	// Check if bean definition exists in this factory.
	// 检查BeanFactory是否存在这个BeanDefinition
	BeanFactory parentBeanFactory = getParentBeanFactory();
	if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
		// Not found -> check parent.
		// 当前容器找不到BeanDefinition,去parent容器查询
		String nameToLookup = originalBeanName(name);
		if (parentBeanFactory instanceof AbstractBeanFactory) {
			return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
					nameToLookup, requiredType, args, typeCheckOnly);
		}
		else if (args != null) {
			// Delegation to parent with explicit args.
			// 返回parent容器的查询结果
			return (T) parentBeanFactory.getBean(nameToLookup, args);
		}
		else {
			// No args -> delegate to standard getBean method.
			return parentBeanFactory.getBean(nameToLookup, requiredType);
		}
	}

	if (!typeCheckOnly) {
		//typeCheckOnly为false的情况,将beanName放在一个alreadyCreated的集合
		markBeanAsCreated(beanName);
	}

	try {
		RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
		checkMergedBeanDefinition(mbd, beanName, args);

		// Guarantee initialization of beans that the current bean depends on.
		// 校验是否配置了 depends-on
		String[] dependsOn = mbd.getDependsOn();
		if (dependsOn != null) {
			for (String dep : dependsOn) {
				// 存在循环引用的情况,要抛出异常
				if (isDependent(beanName, dep)) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
				}
				// 正常情况,注册依赖关系
				registerDependentBean(dep, beanName);
				try {
					// 初始化被依赖项
					getBean(dep);
				}
				catch (NoSuchBeanDefinitionException ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
				}
			}
		}

		// Create bean instance.
		// 单例的Bean
		if (mbd.isSingleton()) {
			sharedInstance = getSingleton(beanName, () -> {
				try {
					// 创建单例bean
					return createBean(beanName, mbd, args);
				}
				catch (BeansException ex) {
					// Explicitly remove instance from singleton cache: It might have been put there
					// eagerly by the creation process, to allow for circular reference resolution.
					// Also remove any beans that received a temporary reference to the bean.
					destroySingleton(beanName);
					throw ex;
				}
			});
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
		// 多例的Bean,scope = protoType
		else if (mbd.isPrototype()) {
			// It's a prototype -> create a new instance.
			Object prototypeInstance = null;
			try {
				// 多例的情况,创建bean之前添加标记(用于循环依赖校验)
				beforePrototypeCreation(beanName);
				 // 执行多例Bean创建
				prototypeInstance = createBean(beanName, mbd, args);
			}
			finally {
				// 创建原型(多例)bean之后擦除标记
				afterPrototypeCreation(beanName);
			}
			bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
		}
		// 如果不是单例bean也不是多例的bean,委托给对应的实现类
		else {
			String scopeName = mbd.getScope();
			if (!StringUtils.hasLength(scopeName)) {
				throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
			}
			Scope scope = this.scopes.get(scopeName);
			if (scope == null) {
				throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
			}
			try {
				Object scopedInstance = scope.get(beanName, () -> {
					beforePrototypeCreation(beanName);
					try {
						// 执行bean创建
						return createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
				});
				bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
			}
			catch (IllegalStateException ex) {
				throw new BeanCreationException(beanName,
						"Scope '" + scopeName + "' is not active for the current thread; consider " +
						"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
						ex);
			}
		}
	}
	catch (BeansException ex) {
		cleanupAfterBeanCreationFailure(beanName);
		throw ex;
	}
}

// Check if required type matches the type of the actual bean instance.
// 检查一下类型是否正确,不正确抛出异常,正确返回实例
if (requiredType != null && !requiredType.isInstance(bean)) {
	try {
		T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
		if (convertedBean == null) {
			throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
		}
		return convertedBean;
	}
	catch (TypeMismatchException ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to convert bean '" + name + "' to required type '" +
					ClassUtils.getQualifiedName(requiredType) + "'", ex);
		}
		throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
	}
}
return (T) bean;

}
源码比较复杂,所以可以带着疑问来跟,首先以单例Bean的情况:#doGetBean.getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一级缓存:singletonObjects (单例池)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 二级缓存:earlySingletonObjects(BeanDefinition还没进行属性填充)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 三级缓存:singletonFactories
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
在某些情况,循环依赖会造成循环调用,所以需要怎么解决?

Spring框架的方法是使用了三级缓存,其实最关键的是earlySingletonObjects一级缓存:singletonObjects,这是Spring BeanDefinition的单例池,首先只保存单例Bean的BeanDefinition,而且这个Bean是一个真正的bean,也就是进行过属性填充的二级缓存:earlySingletonObjects,early从单词意思来说,这个缓存是在singletonObjects之前的,也就是BeanDefinition还没进行属性填充等等操作,Spring引入这个缓存的目的就是为了处理单例bean的循环依赖问题三级缓存:singletonFactories,缓存的是ObjectFactory,表示对象工厂,为什么要加上这个缓存?原因比较复杂,涉及到AOP等等原因,因为我还没理解清楚,所以本文不说明加上了earlySingletonObjects缓存之后,Spring就能支持单例bean的循环依赖,参考语雀某大佬的笔记,画图表示:
带着疑问来跟一下多例Bean的情况:
Spring框架是不支持多例bean的循环依赖的,原因跟下代码:#doGetBean// Fail if we’re already creating this bean instance:
// We’re assumably within a circular reference.
// 校验是否是多例(Prototype)的Bean,多例的bean是不支持循环依赖的
// 为了避免循环依赖,遇到这种情况,直接抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
多例的情况:看代码是通过prototypesCurrentlyInCreation里的数据校验的,prototypesCurrentlyInCreation是一个ThreadLocal对象protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
继续找代码,找到beforePrototypeCreation:protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set beanNameSet = (Set) curVal;
beanNameSet.add(beanName);
}
}
Ctrl+Alt+H,查看这个方法的调用栈:其实就是在#doGetBean就调用了,也就是bean创建之前
try {
// 多例的情况,创建bean之前添加标记(用于循环依赖校验)
beforePrototypeCreation(beanName);
// 执行多例Bean创建
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
// 创建原型(多例)bean之后擦除标记
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
知识点归纳Spring中的循环依赖场景 构造器的循环依赖,通过构造函数Field属性的循环依赖,通过set方法Spring的循环依赖是默认开启的(setAllowCircularReferences)Spring对单例和多例Bean的支持 单例Bean(singleton) :只能通过@Autowired和set方法支持多例Bean(prototype):默认不支持,直接抛异常BeanCurrentlyInCreationExceptionSpring支持单例bean的循环依赖原因:使用了三级缓存IT程序员 分类: 源码学习 标签: Spring源码好文要顶 关注我 收藏该文 smileNicky
关注 - 11
粉丝 - 61 +加关注 1 0

« 上一篇: Spring5.0源码学习系列之浅谈懒加载机制原理 posted @ 2020-11-17 13:35  smileNicky  阅读(124)  评论(1)  编辑  收藏

评论列表    #1楼 47430002020/11/17 下午2:50:292020-11-17 14:50 爱吃甜品的女孩 哈喽,⽼师好,我是开源中国社区运营李艳,了解到您已经发布多篇技术⼲货了,⽂章质量也很⾼,不知道您有没有兴趣参与「源创计划」,让更多开发者看到您的⽂章呢?加入源创计划后,可以让OSCHINA社区的更多开发者看到技术文章。而且入驻只需要授权一次即可,一劳永逸。 支持(0) 反对(0)

刷新评论刷新页面返回顶部 登录后才能发表评论,立即 登录 或 注册, 访问 网站首页。 首页 新闻 博问 专区 闪存 班级 【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】博客园 & 陌上花开HIMMR 给单身的程序员小哥哥助力脱单啦~
【推荐】了不起的开发者,挡不住的华为,园子里的品牌专区
【推荐】未知数的距离,毫秒间的传递,声网与你实时互动
【福利】AWS携手博客园为开发者送免费套餐与抵扣券
【推荐】 阿里云折扣价格返场,错过再等一年
相关博文:
· Spring5ofWebClient(转载)
· Spring5源码阅读环境搭建-gradle构建编译
· spring5源码分析系列(二)——spring核心容器体系结构
· spring5源码分析系列(六)——IOC容器的初始化(四)
· Spring5最新完整教程IDEA版【通俗易懂2019.11月】
» 更多推荐…最新 IT 新闻:
· iPhone 12:比绿更绿
· 负债90亿、万人维权,“蛋壳公寓”会成为下一个ofo吗?
· 谷歌微软腾讯网易吹起的云游戏风口,又一个旧金山卖水人的故事
· 微软新提案:让Windows端Chrome/Edge支持计量连接模式
· 百度财报电话会议实录:直播将成为百度变现的一个很好渠道
» 更多新闻…历史上的今天:
2019-11-17 SpringBoot系列之日志框架使用教程
2019-11-17 SpringBoot系列之集成logback实现日志打印(篇二)
2019-11-17 源码学习系列之SpringBoot自动配置(篇二)

公告

#back-top {
position: fixed;
bottom: 10px;
right: 5px;
z-index: 99;
}
#back-top span {
width: 50px;
height: 64px;
display: block;
background:url(http://images.cnblogs.com/cnblogs_com/seanshao/855033/o_rocket.png) no-repeat center center;
}
#back-top a{outline:none}

    昵称:                     smileNicky         
    园龄:                     6年6个月         
    粉丝:                     61         
    关注:                     11         +加关注

< 2020年11月> 日一二三四五六 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 1 2 3 4 5 6 7 8 9 10 11 12

搜索    常用链接 我的随笔我的评论我的参与最新评论我的标签我的标签Java(11) OAuth2.0(6) Spring Cloud(6) Oracle(4) Docker(3) Android(3) Spring源码(3) 单点登录(2) 架构设计(2) 设计模式(2) 更多 随笔分类 Android(6) C++(2) Dubbo(4) ESB(1) HTTP(1) Java(11) JavaScript(5) Java框架(78) Mysql(17) NIO(1) OAuth2.0(8) Oracle(49) PHP(1) Redis(5) 单点登录(4) 多线程(1) 基础知识(8) 前端(8) 设计模式(27) 搜索引擎(1) 网络安全(2) 我的项目(5) 消息队列(2) 源码学习(6) 随笔档案 2020年11月(3) 2020年10月(1) 2020年9月(2) 2020年8月(2) 2020年7月(14) 2020年6月(14) 2020年5月(2) 2020年4月(10) 2020年1月(10) 2019年12月(11) 2019年11月(27) 2019年10月(10) 2019年9月(14) 2019年8月(11) 2019年7月(13) 2019年6月(11) 2019年5月(14) 2019年4月(14) 2019年3月(8) 2019年2月(20) 2019年1月(10) 2018年12月(19) 2018年11月(3) 2018年10月(1) 2018年9月(1) 2018年8月(1) 2018年5月(11) 2018年1月(2) 2017年12月(3) 2017年11月(2) 2017年10月(1) 2017年8月(1) 2017年7月(1) 2017年6月(2) 2016年5月(1) 2015年11月(8) 2015年10月(1) 2015年7月(1) 2015年6月(2) 2015年5月(1) 阅读排行榜 1. Oracle AWR报告生成和性能分析(14831) 2. 基于权限安全框架Shiro的登录验证功能实现(10609) 3. SpringBoot开源项目(企业信息化基础平台)(9742) 4. mybatis foreach报错It was either not specified and/or could not be found for the javaType Type handler(6903) 5. Mybatis自定义SQL拦截器(6448) 推荐排行榜 1. Oracle索引知识学习笔记(3) 2. IBM WebSphere ESB入门指南(3) 3. OAuth2.0系列之基本概念和运作流程(一)(2) 4. SpringBoot系列之切换log4j日志框架(2) 5. MySQL Explain学习笔记(2)

Copyright © 2020 smileNicky
Powered by .NET Core on Kubernetes

❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄❄

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值