写点什么

Spring 循环依赖为何使用三级缓存?

作者:Java你猿哥
  • 2023-04-24
    湖南
  • 本文字数:4289 字

    阅读完需:约 14 分钟


Spring 解决循环依赖的核心思想在于提前曝光: 1、通过构建函数创建 A 对象(A 对象是半成品,还没注入属性和调用 init 方法)。2、A 对象需要注入 B 对象,发现缓存里还没有 B 对象,将半成品对象 A 放入半成品缓存。3、通过构建函数创建 B 对象(B 对象是半成品,还没注入属性和调用 init 方法)。4、B 对象需要注入 A 对象,从半成品缓存里取到半成品对象 A。5、B 对象继续注入其他属性和初始化,之后将完成品 B 对象放入完成品缓存。6、A 对象继续注入属性,从完成品缓存中取到完成品 B 对象并注入。7、A 对象继续注入其他属性和初始化,之后将完成品 A 对象放入完成品缓存。

一. 在构造 Bean 对象之后,将对象提前曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性和初始化。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory        implements AutowireCapableBeanFactory {    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)            throws BeanCreationException {                    ……// 是否提前曝光        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&                isSingletonCurrentlyInCreation(beanName));        if (earlySingletonExposure) {            if (logger.isTraceEnabled()) {                logger.trace("Eagerly caching bean '" + beanName +                        "' to allow for resolving potential circular references");            }            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));        }        ……    }   }
复制代码

从源码可以得知,doGetBean 最初是查询缓存,一二三级缓存全部查询,如果三级缓存存在则将 Bean 早期引用存放在二级缓存中并移除三级缓存。(升级为二级缓存)

2、提前曝光的对象被放入 Map<String, ObjectFactory<?>> singletonFactories 缓存中,这里并不是直接将 Bean 放入缓存,而是包装成 ObjectFactory 对象再放入

	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);			}		}	}
复制代码

3、为什么要包装一层 ObjectFactory 对象?

如果创建的 Bean 有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是 Spring 无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring 都是在创建好完成品 Bean 之后才创建对应的代理。


这时候 Spring 有两个选择: 1、不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。 2、不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean 就可以按着 Spring 设计原则的步骤来创建。


**Spring 选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?**Spring 就是在对象外面包一层 ObjectFactory,提前曝光的是 ObjectFactory 对象,在被注入时才在 ObjectFactory.getObject 方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存 Map<String, Object> earlySingletonObjects。

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;    }
复制代码

为了防止对象在后面的初始化(init)时重复代理,在创建代理时,earlyProxyReferences 缓存会记录已代理的对象。

     private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
@Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }
复制代码

4、注入属性和初始化

提前曝光之后:1、通过 populateBean 方法注入属性,在注入其他 Bean 对象时,会先去缓存里取,如果缓存没有,就创建该对象并注入。2、通过 initializeBean 方法初始化对象,包含创建代理。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory        implements AutowireCapableBeanFactory {    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)            throws BeanCreationException {        ……// Initialize the bean instance.        Object exposedObject = bean;        try {            populateBean(beanName, mbd, instanceWrapper);            exposedObject = initializeBean(beanName, exposedObject, mbd);        }        catch (Throwable ex) {            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {                throw (BeanCreationException) ex;            }            else {                throw new BeanCreationException(                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);            }        }        ……    }        }    // 获取要注入的对象public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {    protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 一级缓存        Object singletonObject = this.singletonObjects.get(beanName);        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {            synchronized (this.singletonObjects) {// 二级缓存                singletonObject = this.earlySingletonObjects.get(beanName);                if (singletonObject == null && allowEarlyReference) {// 三级缓存                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);                    if (singletonFactory != null) {                        singletonObject = singletonFactory.getObject();                        this.earlySingletonObjects.put(beanName, singletonObject);                        this.singletonFactories.remove(beanName);                    }                }            }        }        return singletonObject;    }}
复制代码

5. 放入已完成创建的单例缓存

在经历了以下步骤之后,最终通过 addSingleton 方法将最终生成的可用的 Bean 放入到单例缓存里。

1、AbstractBeanFactory.doGetBean

2、DefaultSingletonBeanRegistry.getSingleton

3、AbstractAutowireCapableBeanFactory.createBean

4 AbstractAutowireCapableBeanFactory.doCreateBean

5、DefaultSingletonBeanRegistry.addSingleton

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** 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 HashMap<>(16);
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); } }}
复制代码

6.为什么 Sping 不选择二级缓存方式,而是要额外加一层缓存?

如果要使用二级缓存解决循环依赖,意味着 Bean 在构造完后就创建代理对象,这样违背了 Spring 设计原则。 Spring 结合 AOP 跟 Bean 的生命周期,是在 Bean 创建完全之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,在这个后置处理的 postProcessAfterInitialization 方法中对初始化后的 Bean 完成 AOP 代理。


如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理,但是没有出现循环依赖的情况下,设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。


这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象

用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
Spring循环依赖为何使用三级缓存?_Java_Java你猿哥_InfoQ写作社区