聊聊Spring循環依賴三級緩存是否可以減少為二級緩存的情況
基於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; }
常規情況下,會出現以下情況:
通過構建函數創建A對象(A對象是半成品,還沒註入屬性和調用init方法)。
A對象需要註入B對象,發現對象池(緩存)裡還沒有B對象(對象在創建並且註入屬性和初始化完成之後,會放入對象緩存裡)。
通過構建函數創建B對象(B對象是半成品,還沒註入屬性和調用init方法)。
B對象需要註入A對象,發現對象池裡還沒有A對象。
創建A對象,循環以上步驟。
三級緩存
Spring解決循環依賴的核心思想在於提前曝光:
通過構建函數創建A對象(A對象是半成品,還沒註入屬性和調用init方法)。
A對象需要註入B對象,發現緩存裡還沒有B對象,將半成品對象A放入半成品緩存。
通過構建函數創建B對象(B對象是半成品,還沒註入屬性和調用init方法)。
B對象需要註入A對象,從半成品緩存裡取到半成品對象A。
B對象繼續註入其他屬性和初始化,之後將完成品B對象放入完成品緩存。
A對象繼續註入屬性,從完成品緩存中取到完成品B對象並註入。
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有兩個選擇:
不管有沒有循環依賴,都提前創建好代理對象,並將代理對象放入緩存,出現循環依賴時,其他對象直接就可以取到代理對象並註入。
不提前創建好代理對象,在出現循環依賴被其他對象註入時,才實時生成代理對象。這樣在沒有循環依賴的情況下,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. 註入屬性和初始化
提前曝光之後:
通過populateBean方法註入屬性,在註入其他Bean對象時,會先去緩存裡取,如果緩存沒有,就創建該對象並註入。
通過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放入到單例緩存裡。
AbstractBeanFactory.doGetBean -> DefaultSingletonBeanRegistry.getSingleton -> AbstractAutowireCapableBeanFactory.createBean -> AbstractAutowireCapableBeanFactory.doCreateBean -> 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對象?》裡講到有兩種選擇:
不管有沒有循環依賴,都提前創建好代理對象,並將代理對象放入緩存,出現循環依賴時,其他對象直接就可以取到代理對象並註入。
不提前創建好代理對象,在出現循環依賴被其他對象註入時,才實時生成代理對象。這樣在沒有循環依賴的情況下,Bean就可以按著Spring設計原則的步驟來創建。
Sping選擇瞭第二種,如果是第一種,就會有以下不同的處理邏輯:
在提前曝光半成品時,直接執行getEarlyBeanReference創建到代理,並放入到緩存earlySingletonObjects中。
有瞭上一步,那就不需要通過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在生命周期的最後一步完成代理而不是在實例化後就立馬完成代理。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。
推薦閱讀:
- 關於Java Spring三級緩存和循環依賴的深入理解
- Spring解決循環依賴的方法(三級緩存)
- 關於spring循環依賴問題及解決方案
- SpringBean依賴和三級緩存的案例講解
- Java中的Spring 如何處理循環依賴