关于java:Spring循环依赖三级缓存是否可以减少为二级缓存

38次阅读

共计 8396 个字符,预计需要花费 21 分钟才能阅读完成。

基于 Spring-5.1.5.RELEASE

问题

都晓得 Spring 通过 三级缓存 来解决 循环依赖 的问题。然而是不是必须 三级缓存 能力解决,二级缓存 不能解决吗?
要剖析是不是能够去掉其中一级缓存,就先过一遍 Spring 是如何通过 三级缓存 来解决 循环依赖 的。

循环依赖

所谓的 循环依赖 ,就是两个或则两个以上的bean 相互依赖对方,最终造成 闭环。比方“A 对象依赖 B 对象,而 B 对象也依赖 A 对象”,或者“A 对象依赖 B 对象,B 对象依赖 C 对象,C 对象依赖 A 对象”;相似以下代码:

public class A {private B b;}

public class B {private A a;}

惯例状况下,会呈现以下状况:

  1. 通过构建函数创立 A 对象(A 对象是半成品,还没注入属性和调用 init 办法)。
  2. A 对象须要注入 B 对象,发现对象池(缓存)里还没有 B 对象(对象在创立并且注入属性和初始化实现之后,会放入对象缓存里)。
  3. 通过构建函数创立 B 对象(B 对象是半成品,还没注入属性和调用 init 办法)。
  4. B 对象须要注入 A 对象,发现对象池里还没有 A 对象。
  5. 创立 A 对象,循环以上步骤。

三级缓存

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 对象 放入 实现品缓存

其中缓存有三级:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

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


/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
缓存 阐明
singletonObjects 第一级缓存,寄存可用的 成品 Bean
earlySingletonObjects 第二级缓存,寄存 半成品的 Bean半成品的 Bean是已创建对象,然而未注入属性和初始化。用以解决循环依赖。
singletonFactories 第三级缓存,存的是 Bean 工厂对象,用来生成 半成品的 Bean并放入到二级缓存中。用以解决循环依赖。

要理解原理,最好的办法就是浏览源码,从创立 Bean 的办法 AbstractAutowireCapableBeanFactor.doCreateBean 动手。

1. 在结构 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));
        }
        ……
    }   
}     

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

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);
            }
        }
    }
}
public interface ObjectFactory<T> {T getObject() throws BeansException;
}    

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

如果创立的 Bean 有对应的 代理 ,那其余对象注入时,注入的应该是对应的 代理对象 ;然而Spring 无奈提前晓得这个对象是不是有 循环依赖 的状况,而 失常状况 下(没有 循环依赖 状况),Spring都是在创立好 实现品 Bean之后才创立对应的 代理 。这时候Spring 有两个抉择:

  1. 不论有没有 循环依赖 ,都 提前 创立好 代理对象 ,并将 代理对象 放入缓存,呈现 循环依赖 时,其余对象间接就能够取到代理对象并注入。
  2. 不提前创立好代理对象,在呈现 循环依赖 被其余对象注入时,才实时生成 代理对象 。这样在没有 循环依赖 的状况下,Bean就能够按着 Spring 设计准则 的步骤来创立。

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

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
        implements AutowireCapableBeanFactory {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 缓存会记录已代理的对象。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {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);
        }
    }
}    

二级缓存

下面第三步 《为什么要包装一层 ObjectFactory 对象?》 里讲到有两种抉择:

  1. 不论有没有 循环依赖 ,都 提前 创立好 代理对象 ,并将 代理对象 放入缓存,呈现 循环依赖 时,其余对象间接就能够取到代理对象并注入。
  2. 不提前创立好代理对象,在呈现 循环依赖 被其余对象注入时,才实时生成 代理对象 。这样在没有 循环依赖 的状况下,Bean就能够按着 Spring 设计准则 的步骤来创立。

Sping抉择了 第二种 ,如果是 第一种,就会有以下不同的解决逻辑:

  1. 提前曝光半成品 时,间接执行 getEarlyBeanReference 创立到代理,并放入到缓存 earlySingletonObjects 中。
  2. 有了上一步,那就不须要通过 ObjectFactory提早 执行 getEarlyBeanReference,也就不须要singletonFactories 这一级缓存。

这种解决形式可行吗?
这里做个试验,对 AbstractAutowireCapableBeanFactory 做个小革新,在放入 三级缓存 之后立即取出并放入 二级缓存 ,这样 三级缓存 的作用就齐全被疏忽掉,就相当于只有 二级缓存

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));
            // 立即从三级缓存取出放入二级缓存
            getSingleton(beanName, true);
        }
        ……
    }   
}     

测试后果是能够的,并且从源码上剖析能够得出两种形式性能是一样的,并不会影响到 Sping 启动速度。那为什么 Sping 不抉择 二级缓存 形式,而是要额定加一层缓存?
如果要应用 二级缓存 解决 循环依赖 ,意味着 Bean 在 结构 完后就创立 代理对象 ,这样违反了Spring 设计准则。Spring 联合 AOP 跟 Bean 的生命周期,是在Bean 创立齐全 之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来实现的,在这个后置解决的 postProcessAfterInitialization 办法中对初始化后的 Bean 实现 AOP 代理。如果呈现了 循环依赖,那没有方法,只有给 Bean 先创立代理,然而没有呈现循环依赖的状况下,设计之初就是让 Bean 在生命周期的最初一步实现代理而不是在实例化后就立马实现代理。

参考:

《面试官:聊聊 Spring 源码的生命周期、循环依赖》
《面试必杀技,讲一讲 Spring 中的循环依赖》

正文完
 0