Spring5.0源码学习系列之浅谈循环依赖问题

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.5
    • JDK:jdk1.8.0_31
    • Git Server:Git fro window 2.8.3
    • Git Client:SmartGit18.1.5(可选)

3、循环依赖问题

我们可以通过例子进行验证,创建类A:

package com.example.bean;

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

/**
 * <pre>
 *      A class
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/11/05 10:31  修改内容:
 * </pre>
 */
@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;

/**
 * <pre>
 *      B class
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/11/16 14:03  修改内容:
 * </pre>
 */
@Component
public class B {

    //@Autowired
    A a;

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

注册类A、B

package 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;

/**
 * <pre>
 *      AppConfiguration
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/11/05 10:26  修改内容:
 * </pre>
 */
@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;

/**
 * <pre>
 *      TestController
 * </pre>
 *
 * <pre>
 * @author mazq
 * 修改记录
 *    修改后版本:     修改人:  修改日期: 2020/11/05 10:22  修改内容:
 * </pre>
 */
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):默认不支持,直接抛异常BeanCurrentlyInCreationException

8、Spring循环依赖原理

我们通过实验进行了验证,也归纳出了Spring循环依赖的特点,然后具体原因是什么?我们只能通过源码学习得到答案

上一章的学习中,我们对Bean的创建有了一个粗略的了解,所以,顺着这条路线,跟下源码:

在前面的学习,我们知道了{@link org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean}这个方法就是Spring Bean创建的真正执行方法

protected <T> T doGetBean(
		String name, @Nullable Class<T> 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<String> beanNameSet = new HashSet<>(2);
		beanNameSet.add((String) curVal);
		beanNameSet.add(beanName);
		this.prototypesCurrentlyInCreation.set(beanNameSet);
	}
	else {
		Set<String> beanNameSet = (Set<String>) 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):默认不支持,直接抛异常BeanCurrentlyInCreationException
  • Spring支持单例bean的循环依赖原因:使用了三级缓存
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/2616.html原文链接:https://javaforall.net

(0)
上一篇 2020年11月19日 下午10:21
下一篇 2020年11月19日 下午10:27


相关推荐

  • Ubuntu中WPS无法输入中文

    Ubuntu中WPS无法输入中文问题描述 Ubuntu 下安装了 WPS 以及搜狗输入法 在其他软件下可以正常输入中文 但在 wps 下已切换到搜狗输入法 但无法输入中文 原因 环境变量未正确设置 解决方案 wps 文字 sudovim usr bin wps 添加以下文字到打开的文本中 添加到 bin bash 下面 exportXMODIF im fcitx exportQT IM MO

    2026年3月19日
    2
  • 写给大忙人看的 – 搭建文件服务器 MinIO(1)

    写给大忙人看的 – 搭建文件服务器 MinIO(1)还在为搭建文件服务器环境复杂的操作而烦恼吗 下面跟着我一起花三分钟搭建一个超实用的文件服务器一 下载安装中文官网地址 https docs min io cn 下载地址 GNU Linux https dl min io server minio release linux amd64 minioWindows https dl min io server minio release windows amd64 minio exe 赋予文件夹权限 chmod xminio

    2026年3月20日
    3
  • idea整理代码快捷键设置_idea常用快捷键和配置

    idea整理代码快捷键设置_idea常用快捷键和配置问题解决:Ctrl+Alt+L

    2022年10月12日
    38
  • Mongodb地理空间索引

    Mongodb地理空间索引

    2021年11月30日
    46
  • linux chown命令 例子,Linux系统之Chown命令用法介绍

    linux chown命令 例子,Linux系统之Chown命令用法介绍当我们要改变一个文件的属组 我们所使用的用户必须是该文件的属主而且同时是目标属组成员 或超级用户 这种情况的话 只有超级用户的才能改变文件的属主 在 Linux 操作系统的美国服务器当中 Chown 命令是 Linux 美国服务器系统最常用到的命令之一 它是用于改变文件或目录的访问权限 今天小编就简单介绍下 Chown 命令的用法 希望对大家有所帮助 chown 语法 chown 选项 所有者

    2025年11月4日
    6
  • python+pycharm+pyqt5安装教程「建议收藏」

    python+pycharm+pyqt5安装教程「建议收藏」本文描述Windows系统下如何安装Python+PyCharm+PyQt5,并通过PyQt5采用两种方式设计GUI界面:1.直接使用代码设计界面2.先使用QtDesigner进行可视化设计,然后将生成的.ui文件转换成.py文件安装Python+PyCharm+PyQt51、安装Python访问官网https://www.python.org/,下载并安装你的目标Python版本。2、安装PyQt51)进入cmd界面。执行命令pipinstallpyqt5pyq

    2022年8月25日
    10

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号