文章目录

  • 前言
  • 一、什么是Bean的循环依赖
  • 二、Spring Bean的三级缓存
  • 总结
  • 思考:



前言

对于高级开发来说以后可能需要做架构,所以需要对部分常用优秀框架的底层原理有一定的了解,Spring是其中的重中之重,在面试的过程中肯定会问许多Spring源码的知识,Spring中Bean的循环依赖也是中高级开发面试的高频问题,本文就讲解一下Spring的循环依赖问题。

一、什么是Bean的循环依赖

对于Bean的依赖大家应该都很清楚,意思是注入的Bean对象依赖另一个注入的Bean对象,直白的说就是容器中注入的Bean对象中用到了另一个容器中注入的Bean对象,常见的后台的三层开发,Dao -> Service -> Controller,这就叫做依赖。那么什么是循环依赖呢?循环依赖说白了就是依赖死循环了,例如存在A对象中有属性B对象,而B对象中有属性A对象,这种依赖关系就是循环依赖。

@Component
class TestA{
   @Autowired
	private TestB testB;
}
@Component
class TestB{
	@Autowired
	private TestA testA;
}

public static void main(String[] args) {
	ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
	TestA testA = applicationContext.getBean(TestA.class);
	TestB testB = applicationContext.getBean(TestB.class);
	System.out.println(testA);
	System.out.println(testB);
}

像这种情况下测试是可以正常运行的,但是我们把A对象和B对象都改为多例的情况呢?下面把A和B都加上多例注解,再次运行

@Scope("prototype")

多例情况下报错了:Requested bean is currently in creation: Is there an unresolvable circular reference?

java中bean循环依赖 bean的循环依赖_Spring


试想一下创建A对象需要B对象,创建B对象需要A对象,究竟先创建谁?为什么单例的时候不会产生循环依赖问题呢?单例的时候是怎么处理的呢?

java中bean循环依赖 bean的循环依赖_java中bean循环依赖_02

二、Spring Bean的三级缓存

容器加载创建Bean对象的时候在Spring的源码DefaultSingletonBeanRegistry类中有这么一段代码,上面明确的写着注释:返回在给定名称下注册的(原始)单例对象。检查已经实例化的单例,还允许对当前创建的单例进行早期引用(解析循环引用)
看一下三级缓存分别是哪三级:

//完全创建成功的单例对象,属性已经设置值完毕
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存,对象创建工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//早期的单例对象,只创建了对象,没设置属性值,类似于页面布局先画框占个空
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

看一下三级缓存的使用:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 首先看单例对象是否被完整创建了
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		//完整的单例对象没有被创建,看空壳的单例对象
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			//双重校验锁,在获取的过程中万一被创建了呢
			synchronized (this.singletonObjects) {
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						//早产的单例对象也没有创建,那就让工厂先造出个空壳来,占好位置
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						if (singletonFactory != null) {
							singletonObject = singletonFactory.getObject();
							//放入空壳缓存中
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}

来看一下是三级缓存怎么使用的:

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
			
		// 检查并且手动注册单例对象
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			...
		}
		else {
			//多例情况下直接报上面的错误,这就是为啥多例的时候不行了
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}
			...
			try {
				...
				//直接看一下创建对象,两个方法 getSingleton()和createBean()
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						..
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
			...
	}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				...
				//创建前的准备,将当前对象标记为创建中
				beforeSingletonCreation(beanName);
				...
			}
			return singletonObject;
		}
	}

下面就涉及到前两天说的Bean的生命周期了

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		//前面讲过在这里执行无参构造函数创建空对象
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		...
		//看这里,起的名字也可以看出来,单例&&需要解决循环依赖问题&&正在创建中的,前面已经标记了
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			//看这就是造壳的工厂,第三级缓存已经装进去了
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
		Object exposedObject = bean;
		try {
			//下面开始属性赋值了,在属性赋值的时候对于TestB进行了初始化,就不继续分析了
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		...
		return exposedObject;
	}

在源码中写个打印语句并打个断点分析一下:

java中bean循环依赖 bean的循环依赖_Spring_03

java中bean循环依赖 bean的循环依赖_Spring_04


第一次进来是初始化的TestA,初始化TestA的时候只有TestA的工厂,但是在对A中B属性进行装配的时候,发现B也是个注入的对象,但是还没有初始化,所以又把B初始化了一次,这时候存在了两个工厂,所以在B对象注入A的时候注入了A的空壳子,接下来A对象又注入了B的空壳子。

总结

java中bean循环依赖 bean的循环依赖_Spring_05


如上图Spring中解决单例对象循环引用问题是通过先注入的空壳子不带有属性的对象,后面又进行的属性填充来解决的,由于对象是地址引用的,所以TestA注入完TestB对象后,TestB中对应的TestA同步可以被修改。多例的为什么不这样搞呢?因为Spring中并没有对多例的缓存,缓存多例对象没有多大意义,而且不知道最终会有多少个对象,很容易内存溢出。

思考:

同时还有一种情况TestA和TestB同时存在的情况会使怎样的呢?这个可以提示一下跟单例A和多例B谁先初始化创建获取有关系。

java中bean循环依赖 bean的循环依赖_循环引用_06