《Spring 高手系列》(循环依赖)笔记

前言

参考链接1

问题

  1. 什么是循环依赖?
  2. 如何检测是否存在循环依赖?
  3. 如何解决循环依赖?
  4. 多例能否解决循环依赖?
  5. 单例的情况下,虽然可以解决循环依赖,是否存在其他问题?
  6. 为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?

1.什么是循环依赖?

A依赖B,B依赖C,C依赖A,形成一个环形。

2.如何检测是否存在循环依赖?

单例bean

检测原理:使用一个列表来记录正在创建中的bean,bean创建之前,先去记录中看一下自己是否已经在列表中了,如果在,说明存在循环依赖,如果不在,则将其加入到这个列表,bean创建完毕之后,将其再从这个列表中移除。

源码:DefaultSingletonBeanRegistry类中有一个beforeSingletonCreation方法

	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}
	
	public BeanCurrentlyInCreationException(String beanName) {
		super(beanName,
				"Requested bean is currently in creation: Is there an unresolvable circular reference?");
	}
	
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

	private final Set<String> inCreationCheckExclusions =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

我们来看这个表达式,第一个条件是inCreationCheckExclusions不包含这个bean,第二个条件是singletonsCurrentlyInCreation不能添加这个bean,这个时候会抛出一个异常。我们在异常这里打一个断点观察

在这里插入图片描述
当前正在创建的set中无法添加这个bean,满足条件二。排除set里面没有值,不包含这个bean,满足第一个条件。

非单例bean

AbstractBeanFactory类中的 doGetBean 方法

	protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
	 @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {}

273 - 275 行

			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

348 - 359 行

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

3.Spring如何解决循环依赖的问题

spring创建bean主要的几个步骤:

  • 步骤1:实例化bean,即调用构造器创建bean实例

  • 步骤2:填充属性,注入依赖的bean,比如通过set方式、@Autowired注解的方式注入依赖的bean

  • 步骤3:bean的初始化,比如调用init方法等。

从上面3个步骤中可以看出,注入依赖的对象,有2种情况:

  • 通过步骤1中构造器的方式注入依赖

  • 通过步骤2注入依赖

构造器方式

创建三个Service类,让他们循环依赖,a->b->c。下面给出A的示例,其他两个类似。

@Service
public class AaService {

    BbService bbService;

    @Autowired
    public AaService(BbService bbService) {
        this.bbService = bbService;
    }
}

启动后会看到下面这样的输出,创建失败。

在这里插入图片描述

set方式

创建三个Service类,让他们循环依赖,d->e->f。下面给出D的示例,其他两个类似。

@Service
public class DdService {

    EeService eeService;

    @Autowired
    public void setEeService(EeService eeService) {
        this.eeService = eeService;
    }
}

启动后会看到下面这样的输出,创建失败。

在这里插入图片描述
如果将方法上的注解移动到字段上时是可以启动成功的。

强制忽略循环依赖

spring:
  main:
    allow-circular-references: true

循环依赖无法解决的情况

只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。

探讨:为什么需要用3级缓存

如果只使用2级缓存,直接将刚实例化好的bean暴露给二级缓存出是否可以否?

先下个结论吧:不行。

这样做是可以解决:早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。

若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。

单例bean解决了循环依赖,还存在什么问题?

循环依赖的情况下,由于注入的是早期的bean,此时早期的bean中还未被填充属性,初始化等各种操作,也就是说此时bean并没有被完全初始化完毕,此时若直接拿去使用,可能存在有问题的风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值