Spring循环依赖,你真的懂了吗?

本文详细解释了Spring框架中的循环依赖问题,介绍了如何通过三级缓存机制(singletonObjects,earlySingletonObjects,SingletonFactories)来解决依赖注入过程中的循环依赖,并指出其局限性,适用于setter和field注入,但不适用于构造器注入的场景。
摘要由CSDN通过智能技术生成

目录

Spring Bean循环依赖问题

Spring 三级缓存机制

概念

原理

局限性


Spring Bean循环依赖问题

        循环依赖,就是对象之间相互持有对方的引用,这在Java中本就是很正常的事情,为什么在Spring中就是难题了呢?

@Service
public class OrderService{
    @Autowired
    private StockService stockService;
}

@Service
public class StockService{
    @Autowired
    private OrderService orderService;
}

        这是因为Spring引入了依赖注入机制,它会在实例化bean之后自动为其属性赋值。如果此时出现了bean相互依赖的话,以上述代码为例,当为OrderService bean进行依赖注入时发现其中有StockService类型的属性,就会根据stockService属性的类型和名称从Spring容器中获取对应的单例bean,如果Spring容器中已经存在这样的单例bean,那直接拿来赋值即可,但如果不存在,就需要立即创建一个StockService bean,当为StockService bean进行依赖注入时,发现其中有OrderService类型的属性,需要从Spring容器中获取OrderService类型的单例bean来为其赋值,而触发我们创建StockService bean的条件正是创建OrderService bean过程中的依赖注入,这里就产生了循环依赖问题。

图片

Spring 三级缓存机制

        针对上述场景,Spring通过三级缓存和提前曝光bean对象的机制解决了部分循环依赖的问题。

概念

图片

        我们说的三级缓存,实际上就是在创建bean的过程中用来存储不同阶段bean对象的三个map:

  • singleObjects:用来缓存已经经历过完整生命周期的bean实例,可以直接提供给用户使用;

  • earlySingleObjects:用来缓存已经经过实例化但属性还没有被自动赋值的bean,只用来提前曝光,不能提供给用户使用;

  • SingletonFactories:存放的是ObjectFactory匿名内部类的实例,调用它的getObject()方法最终会调到getEarlyBeanReference()方法,该方法可以获取提前暴露的单例bean引用。

图片

原理

        还是上面的示例,当我们多加入一层缓存后会有什么样的效果?

        在为OrderService bean依赖注入之前,先把它的原始对象放入缓存,这样提前暴露出来就可以供其他bean使用了,之后在进行依赖注入时由于依赖了StockService的bean,如果该bean还不存在,就需要被立刻创建,创建StockService bean的过程同OrderService,也会将它的原始对象放入缓存提前暴露出来,然后在对StockService bean进行依赖注入的过程中就可以从缓存中拿到OrderService bean的原始对象直接用来进行属性赋值,因为在我们的示例中,自始至终都是同一个OrderService原始对象,即使完成后续生命周期,该对象在堆中也不会变化,因此StockService bean的依赖注入就可以顺利完成了,同理,OrderService bean也是一样。

图片

        从我们的示例中可以看出,明明多添加一个缓存就可以解决问题,为什么Spring要“多此一举”再加两个缓存呢?

        试想一下,在上面的示例中,如果我们需要对OrderService bean进行AOP操作产生代理对象,那么此时Spring容器中的OrderService bean应该是经过AOP生成的代理对象,而非原始对象。同时我们需要明确一点,AOP是在bean的初始化后过程中才执行的操作,要晚于依赖注入,因此此时StockService bean中所依赖的OrderService bean和我们当前经过AOP生成的代理对象完全不是一码事。正是基于这样的原因,Spring才引入了三级缓存——singletonFactories。

        我们在上面提到过,singletonFactories中存的是某个beanName对应的ObjectFactory。当生成完原始对象之后,就会构造一个对应的ObjectFactory并添加到 singletonFactories 中,如下所示:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

        当执行该表达式时就会去调用getEarlyBeanReference()方法,最终会执行SmartInstantiationAwareBeanPostProcessor接口中的 getEarlyBeanReference() 方法,如果该bean无需 AOP的话,返回的就是原bean对象;如果该bean有 AOP的话,返回的就是AOP之后的代理对象。

        在整个Spring中,默认就只有 AbstractAutoProxyCreator 真正实现了getEarlyBeanReference() 方法,用来进行AOP,并在此之前将当前beanName和原始bean对象加入到earlyProxyReferences缓存中,表示已经提前代理,后续不需要再去AOP了。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}
// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}

        具体在那个时机才会执行getEarlyBeanReference()方法呢?

        还是回到我们示例中,当生成完OrderService bean的原始对象后,就会构造一个对应的ObjectFactory并添加到 singletonFactories 中,这个ObjectFactory可能会用到也可能用不到。中间过程略过,当为StockService进行依赖注入时,会从singletonFactories 根据beanName得到一个ObjectFactory,然后执行ObjectFactory的getObject()方法,本质上执行的是getEarlyBeanReference() 方法,此时会得到一个OrderService原始对象经过AOP之后的代理对象,然后把该代理对象添加到 earlySingletonObjects 中,之所以添加到 earlySingletonObjects中,是因为此时OrderService原始对象还没有完成属性填充等步骤,即还没有经历完整的生命周期,所以不能直接把OrderService的代理对象放入singletonObjects 中。假设现在还有其他对象依赖了OrderService,那么就可以从earlySingletonObjects中直接得到OrderService 原始对象的代理对象了。

        当StockService bean创建好之后,会继续按照bean生命周期顺序为OrderService bean进行依赖注入等操作,当走到AOP这一步时,由于OrderService beanName已经存在于earlyProxyReferences中,即已经提前进行过了AOP,所以此时会直接略过,不会进行二次AOP。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

        当OrderService bean创建好之后,就可以从earlySingletonObjects中得到代理对象,然后添加到singletonObjects中。

​​​​​

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

图片

局限性

  1. Spring的三级缓存机制主要针对的是已经实例化但尚未完全初始化的对象,因此处理通过 setter 注入或字段注入的循环依赖中非常有效,因为这些注入方式可以在对象构造之后进行。但如果使用构造器注入,必须在构造器调用前就有完整的依赖对象,如果依赖对象尚未创建完成,那么此时也就无法完成构造过程,从而导致无法实例化任何一个依赖的对象。并且在构造函数调用完成之前,对象实际并不存在,因此也无法将其存放在任何缓存中。

  2. Spring的三级缓存机制主要设计用于解决单例(singleton)Bean 的循环依赖问题,而不是为原型(prototype)Bean 设计的。原型作用域的 Bean 每次从容器中请求时都会创建一个新的实例。这意味着每个原型 Bean 的实例都是独立的,与其他请求中的 Bean 实例无关。原型 Bean的循环依赖是一个更加复杂的问题,如果原型 Bean A 依赖于原型 Bean B,而 B 又依赖于 A,那么在创建 A 的过程中尝试创建 B(反之亦然)将会导致无限循环的递归创建过程。如果存在这种情况,Spring会抛出一个异常来表示依赖关系配置错误。


欢迎关注微信订阅号:技术勘察馆

  • 41
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值