zoukankan      html  css  js  c++  java
  • Spring Bean 循环依赖为什么需要三级缓存

    Spring Bean 循环依赖为什么需要三级缓存

    这里指的是单例的、非构造依赖的循环引用。很多人都知道Spring用了三层缓存来解决循环依赖,但是不知道其原因,为什么是三级缓存?二级缓存不行吗?一级缓存不可以 ?

    三级缓存

    Spring 解决循环依赖的核心就是提前暴露对象,而提前暴露的对象就是放置于第二级缓存中。缓存的底层都是Map,至于它们属于第几层是由Spring获取数据顺序以及其作用来表现的。

    三级缓存的说明:

    名称 作用
    singletonObjects 一级缓存,存放完整的 Bean。
    earlySingletonObjects 二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 初始化(init) 方法。
    singletonFactories 三级缓存,存放的是 Bean 工厂,主要是生产 Bean,存放到二级缓存中。

    DefaultSingletonBeanRegistry类中:

    	/** Cache of singleton objects: bean name to bean instance. */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    	/** Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    	/** Cache of early singleton objects: bean name to bean instance. */
    	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    

    为什么使用三级缓存?

    第一 先说说第一级缓存singletonObjects

    1.先说一级缓存singletonObjects。实际上,一级依赖已经可以解决循环依赖的问题,假设两个beanA和beanB相互依赖,beanA被实例化后,放入一级缓存,即使没有进行初始化,但是beanA的引用已经创建(栈到堆的引用已经确定),其他依赖beanB已经可以持有beanA的引用,但是这个bean在没有初始化完成前,其内存(堆)里的字段、方法等还不能正常使用,but,这并不影响对象之间引用持有;这个时候beanA依赖的beanB实例化,beanB可以顺利拿到beanA的引用,完成beanB的实例化与初始化,并放入一级缓存,在beanB完成创建后,beanA通过缓存顺利拿到beanB的引用,至此,循环依赖只需一层缓存就能完成。

    2.一级缓存的关键点在与:bean实例化与初始化的分离。从JVM的角度,实例化后,对象已经存在,其内的属性都是初始默认值,只有在初始化后才会赋值,以及持有其他对象的引用。通过这个特性,在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。

    第二 说说第三级缓存singletonFactories

    1.上述我们通过一级缓存已经拿到的对象有什么问题?

    根本问题就是,我们拿到的是bean的原始引用,如果我们需要的是bean的代理对象怎么办?Spring里充斥了大量的动态代理模式的架构,典型的AOP就是动态代理模式实现的,再比如我们经常使用的配置类注解@Configuration在缺省情况下(full mode),其内的所有@Bean都是处于动态代理模式,除非手动指定proxyBeanMethods = false将配置转成简略模式(lite mode)。

    2.所以,Spring在bean实例化后,将原始bean放入第三级缓存singletonFactories中,第三级缓存里实际存入的是ObjectFactory接口签名的回调实现。

    # 函数签名
    addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
        
    # 具体实现由回调决定    
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    

    那么如果有动态代理的需求,里面可以埋点进行处理,将原始bean包装后返回。

    	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    		Object exposedObject = bean;
    		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    			for (BeanPostProcessor bp : getBeanPostProcessors()) {
    				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
    					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
    					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
    				}
    			}
    		}
    		return exposedObject;
    	}
    

    3.通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。

    第三 说说第二级缓存earlySingletonObjects

    1.为什么需要earlySingletonObjects这个二级缓存?并且,如果只有一个缓存的情况下,为什么不直接使用singletonFactories这个缓存,即可实现代理又可以缓存数据。

    2.从软件设计角度考虑,三个缓存代表三种不同的职责,根据单一职责原理,从设计角度就需分离三种职责的缓存,所以形成三级缓存的状态。

    3、再次说说三级缓存的划分及其作用。

    一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。

    二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。

    三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。

    在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。

    总结

    还不到总结的时候... ...

  • 相关阅读:
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale
    CodeForces 785B Anton and Classes
    CodeForces 785A Anton and Polyhedrons
    爱奇艺全国高校算法大赛初赛C
    爱奇艺全国高校算法大赛初赛B
    爱奇艺全国高校算法大赛初赛A
    EOJ 3265 七巧板
    EOJ 3256 拼音魔法
    EOJ 3262 黑心啤酒厂
  • 原文地址:https://www.cnblogs.com/asker009/p/14376955.html
Copyright © 2011-2022 走看看