Spring循環依賴三級緩存是否能夠減小爲二級緩存?

基於Spring-5.1.5.RELEASE

問題

都知道Spring經過三級緩存來解決循環依賴的問題。可是是否是必須三級緩存才能解決,二級緩存不能解決嗎?
要分析是否是能夠去掉其中一級緩存,就先過一遍Spring是如何經過三級緩存來解決循環依賴的。java

循環依賴

所謂的循環依賴,就是兩個或則兩個以上的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解決循環依賴的核心思想在於提早曝光app

  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對象放入完成品緩存

其中緩存有三級:ide

/** 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有兩個選擇:post

  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. 注入屬性和初始化

提早曝光以後:this

  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作個小改造,在放入三級緩存以後馬上取出並放入二級緩存,這樣三級緩存的做用就徹底被忽略掉,就至關於只有二級緩存
image

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中的循環依賴
相關文章
相關標籤/搜索