Spring-循环依赖之三级缓存

本文深入探讨Spring框架中如何通过三级缓存机制解决singleton注入的循环依赖问题,剖析构造器注入和prototype注入为何无法利用此机制。

三级缓存处理循环依赖

为什么会存在循环依赖的问题呢?

假如现在有两个类,A和B,A里面引用了B,B里面引用了A,A初始化的时候会注入B,B还没有创建,就马上开始创建B的过程,在创建B时,又需要注入A,而A还没有创建完成,就形成了循环依赖,也就是循环引用。

Spring循环依赖有三种情况:

  1. 构造器注入
  2. singleton注入(setter注入单例,spring注入的默认方式)
  3. prototype注入(setter注入原型)

注意,循环依赖只能解决singleton注入形成的循环依赖

三级缓存:

//一级缓存,所有初始化好的Bean(可以直接使用)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//二级缓存,根据名称可知,用于存放提前曝光的单例对象的map,实例化还没有填充属性,
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
//三级缓存,单例对象工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
//正在创建中的单例对象的名称集合,创建完成会被移除
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
//创建成功的单例对象的名称集合
private final Set<String> registeredSingletons = new LinkedHashSet(256);

singleton注入利用三级缓存解决循环依赖

借助网图给大家展示下创建A和B的过程在这里插入图片描述

实例化A(正常实例化)到第十步的时候发现依赖了B,然后在第九步执行了addSingletonFactory()方法

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	...
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized(this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }

        }
    }
    ...
}

在这个方法里面,就将工厂Bean放到了singletonFactories集合里面,执行到这里

//一级缓存
singletonObjects//没有A
//二级缓存
earlySingletonObjects//没有A
//三级缓存
singletonFactories//有A
//正在创建中的单例对象的名称集合
singletonsCurrentlyInCreation//有A
//创建成功的单例对象的名称集合
registeredSingletons//有A

然后执行第十步,发现没有B的实例,就开始创建B,到第十步之前也都是正常流程,

到第十步,当执行到autowireByName()方法时要调用getBean()方法去容器里面找A

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    ...
    protected void autowireByName(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
        String[] propertyNames = this.unsatisfiedNonSimpleProperties(mbd, bw);
        String[] var6 = propertyNames;
        int var7 = propertyNames.length;

        for(int var8 = 0; var8 < var7; ++var8) {
            String propertyName = var6[var8];
            if (this.containsBean(propertyName)) {
                Object bean = this.getBean(propertyName);//调用getBean()
                pvs.add(propertyName, bean);
                this.registerDependentBean(propertyName, beanName);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Added autowiring by name from bean name '" + beanName + "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
                }
            } else if (this.logger.isTraceEnabled()) {
                this.logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName + "' by name: no matching bean found");
            }
        }
    }
    ...
}

而最终getBean()方法调用getSingleton()方法,这里是重点

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);//一级缓存里面找
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);//二级缓存里面找
                if (singletonObject == null && allowEarlyReference) {
                    //三级缓存里面找
                    ObjectFactory<?> singletonFactory =(ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //将三级缓存里面的剪切到二级缓存里面
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }
}

执行这里时earlySingletonObjects里面是有A的,只是这里面的A是不完美的,还没有完成属性填充,但是已经可以被引用了,最终这里返回A的引用给B,然后B完成初始化,在吧B的引用返回给A,最终A也完成初始化,然后就不存在循环依赖。

为什么构造器注入不能利用三级缓存解决循环依赖问题呢?

因为上面的三级缓存集合singletonFactories的前提就是执行了构造器,所以构造器的循环依赖没法解决。

为什么prototype注入(setter注入原型)不能利用三级缓存解决循环依赖问题呢?

原型注入的意思就是每一次请求都要创建一个实例,对于“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

为什么不能用二级缓存或者一级缓存解决循环依赖?

如果是一级缓存,那么一个map里面肯定有已经完成初始化了的Bean和实例化了但是还没有完成属性注入的Bean,此时其他需要来这里获取Bean时可能会拿到还没有属性注入的Bean,会导致抛空指针异常,所以一级缓存是不行的。

其实二级缓存也是可以解决循环依赖的,但是为了方便用户自己拓展,所以运用了三级缓存,

关于为什么不用二级缓存,建议阅读–》》》https://www.cnblogs.com/grey-wolf/p/13034371.html

### Spring 框架中的三级缓存机制 Spring 容器通过其内部实现的三级缓存来解决单例 Bean 的循环依赖问题。以下是关于这一主题的具体说明: #### 单例 Bean 循环依赖概述 在 Spring 中,当两个或多个 Bean 形成相互依赖的关系时,就会发生循环依赖。例如,Bean A 依赖于 Bean B,而 Bean B 又反过来依赖于 Bean A。这种情况下,默认构造函数注入可能会导致实例化失败。 为了处理这种情况,Spring 提供了一种基于 **三级缓存** 的解决方案[^1]。 #### 三级缓存的工作原理 Spring 使用三个不同的缓存结构来存储正在创建和已经完成初始化的 Bean 实例。这些缓存分别是: 1. **`singletonObjects`**: 存储完全初始化后的单例对象。 2. **`earlySingletonObjects`**: 存储尚未完成初始化的对象(即早期暴露的对象)。 3. **`singletonFactories`**: 存储用于延迟加载的工厂方法。 具体流程如下: - 当容器尝试获取某个 Bean 时,它会先检查 `singletonObjects` 缓存是否存在该 Bean。如果存在,则直接返回。 - 如果不存在,继续检查 `earlySingletonObjects` 和 `singletonFactories` 是否有对应的占位符或提前曝光的 Bean 实例。 - 在实际创建过程中,Spring 首先将当前未完成的 Bean 放入 `singletonFactories` 或者 `earlySingletonObjects` 中作为临时状态,以便其他 Bean 能够访问到这个部分构建好的实例。 一旦所有的依赖都已解析完毕并完成了必要的后置处理器调用之后,最终完整的 Bean 就会被移至 `singletonObjects` 并清理掉中间阶段使用的缓存条目[^2]。 #### 示例代码展示 下面是一个简单的例子演示如何配置以及可能触发上述机制的情况: ```java @Component public class ServiceA { private final ServiceB serviceB; @Autowired public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; } } @Component public class ServiceB { private final ServiceA serviceA; @Autowired public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; } } ``` 在这个场景下,如果没有特别设置允许此类情况(Such as setting primary or qualifier annotations),则默认情况下,Spring能够自动利用它的三重缓冲策略去妥善管理这两个互相引用的服务组件. #### 局限性和注意事项 尽管如此强大的功能可以帮助我们简化很多复杂关系的设计考量,但也并非适用于所有场合: - 原型(Prototype)作用域下的 bean 不支持此方式解决循环依赖. - 对某些自定义生命周期或者特殊需求的应用程序来说,过度依赖框架自带的行为可能导致不可预测的结果.[^3] ### 结论 综上所述,Spring Framework确实具备一套完善的体系用来应对常见的开发难题之一——循环依赖问题;借助精心设计过的多级内存映射技术实现了优雅高效的资源调配方案.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值