Spring三級緩存解決循環依賴
我們都知道Spring中的BeanFactory是一個IOC容器,負責創建Bean和緩存一些單例的Bean對象,以供項目運行過程中使用。
創建Bean的大概的過程:
- 實例化Bean對象,為Bean對象在內存中分配空間,各屬性賦值為默認值
- 初始化Bean對象,為Bean對象填充屬性
- 將Bean放入緩存
首先,容器為瞭緩存這些單例的Bean需要一個數據結構來存儲,比如Map {k:name; v:bean}。
而我們創建一個Bean就可以往Map中存入一個Bean。這時候我們僅需要一個Map就可以滿足創建+緩存的需求。
但是創建Bean過程中可能會遇到循環依賴問題,比如A對象依賴瞭一個B對象,而B對象內部又依賴瞭一個A,如下:
public class A { B b; } public class B { A a; }
假設A和B我都定義為單例的對象,並且需要在項目啟動過程中自動註入,如下:
@Component public class A { @Autowired B b; } @Component public class B { @Autowired A a; }
一級緩存
- 實例化A對象。
- 填充A的屬性階段時需要去填充B對象,而此時B對象還沒有創建,所以這裡為瞭完成A的填充就必須要先去創建B對象;
- 實例化B對象。
- 執行到B對象的填充屬性階段,又會需要去獲取A對象,而此時Map中沒有A,因為A還沒有創建完成,導致又需要去創建A對象。
- 這樣,就會循環往復,一直創建下去,隻到堆棧溢出。
為什麼不能在實例化A之後就放入Map?
因為此時A尚未創建完整,所有屬性都是默認值,並不是一個完整的對象,在執行業務時可能會拋出未知的異常。所以必須要在A創建完成之後才能放入Map。
二級緩存
此時我們引入二級緩存用另外一個Map2 {k:name; v:earlybean} 來存儲尚未已經開始創建但是尚未完整創建的對象。
- 實例化A對象之後,將A對象放入Map2中。
- 在填充A的屬性階段需要去填充B對象,而此時B對象還沒有創建,所以這裡為瞭完成A的填充就必須要先去創建B對象。
- 創建B對象的過程中,實例化B對象之後,將B對象放入Map2中。
- 執行到B對象填充屬性階段,又會需要去獲取A對象,而此時Map中沒有A,因為A還沒有創建完成,但是我們繼續從Map2中拿到尚未創建完畢的A的引用賦值給a字段。這樣B對象其實就已經創建完整瞭,盡管B.a對象是一個還未創建完成的對象。
- 此時將B放入Map並且從Map2中刪除。
- 這時候B創建完成,A繼續執行b的屬性填充可以拿到B對象,這樣A也完成瞭創建。
- 此時將A對象放入Map並從Map2中刪除。
二級緩存已然解決瞭循環依賴問題,為什麼還需要三級緩存?
從上面的流程中我們可以看到使用兩級緩存可以完美解決循環依賴的問題,但是Spring中還有另外一個問題需要解決,這就是初始化過程中的AOP實現。
AOP是Spring的重要功能,實現方式就是使用代理模式動態增強類的功能。
動態單例目前有兩種技術可以實現,一種是JDK自帶的基於接口的動態Proxy技術,一種是CGlib基於字節碼動態生成的Proxy技術,這兩種技術都是需要原始對象創建完畢,之後基於原始對象生成代理對象的。
那麼我們發現,在二級緩存的設計下,我們需要在放入緩存Map之前將代理對象生成好。
將流程改為:
- 實例化Bean對象,為Bean對象在內存中分配空間,各屬性賦值為默認值
- 如果有動態單例,生成Bean對象的代理Proxy對象
- 初始化Proxy對象,為Bean對象填充屬性
- 將Proxy放入緩存
這樣雖然也可以解決,AOP的問題,但是我們知道Spring中AOP的實現是通過後置處理器BeanPostProcessor機制來實現的,而後置處理器是在填充屬性結束後才執行的。流程如下:
- 實例化對象
- 對象填充屬性
- BeanPostProcessor doBefore
- init-method
- BeanPostProcessor doAfter — AOP是在這個階段實現的
所以要實現上面的方案,勢必需要將BeanPostProcessor階段提前或者侵入到填充屬性的流程中,那麼從程序設計上來說,這樣做肯定是不美的。
三級緩存
Spring引入瞭第三級緩存來解決這個問題, Map3 {k:name v:ObjectFactory} ,這個緩存的value就不是Bean對象瞭,而是一個接口對象由一段lamda表達式實現。在這段lamda表達式中去完成一些BeanPostProcessor的執行。
- 實例化A對象之後,將A的ObjectFactory對象放入Map3中。
- 在填充A的屬性階段需要去填充B對象,而此時B對象還沒有創建,所以這裡為瞭完成A的填充就必須要先去創建B對象。
- 創建B對象的過程中,實例化B的ObjectFactory對象之後,將B對象放入Map2中。
- 執行到B對象填充屬性階段,又會需要去獲取A對象,而此時Map1中沒有A,因為A還沒有創建完成,但是我們繼續從Map2中也拿不到,到Map3中獲取瞭A的ObjectFactory對象,通過ObjectFactory對象獲取A的早期對象,並將這個早期對象放入Map2中,同時刪除Map3中的A,將尚未創建完畢的A的引用賦值給a字段。這樣B對象其實就已經創建完整瞭,盡管B.a對象是一個還未創建完成的對象。
- 此時將B放入Map並且從Map3中刪除。
- 這時候B創建完成,A繼續執行b的屬性填充可以拿到B對象,這樣A也完成瞭創建。
- 此時將A對象放入Map並從Map2中刪除。
源碼步驟解析
- SpringBoot項目啟動執行到SpringApplication#run 中的refreshContext(context);,最終調用Spring容器的AbstractApplicationContext#refresh方法,開始初始化BeanFactory。
- 在AbstractApplicationContext#refresh步驟中,執行到AbstractApplicationContext#finishBeanFactoryInitialization方法,開始完成 Bean 工廠初始化。
- 執行到AbstracBeanFactory.preInstantiateSingletons(),開始根據BeanFactory中的BeanDefinition信息初始化Bean對象。
- 在AbstracBeanFactory.preInstantiateSingletons()方法中,發現A對象的BeanDefinition,執行AbstracBeanFactory.getBean方法,獲取A對象。
- 在AbstracBeanFactory.getBean方法中,執行AbstracBeanFactory.doGetBean方法,獲取A對象。
- 在AbstracBeanFactory.doGetBean方法中執行DefaultSingletonBeanRegistry#getSingleton方法,嘗試從緩存中獲取A對象的單例對象緩存。
- 到一級緩存singletonObjects中找,未找到;
- 到二級緩存earlySingletonObjects中找,未找到;
- 到三級緩存singletonFactories中找,未找到;
- 再次執行DefaultSingletonBeanRegistry#getSingleton的重載方法,傳入lamda表達式形式的ObjectFactory對象,內部調用AbstractAutowireCapableBeanFactory#createBean方法,嘗試創建A對象。
- 在AbstractAutowireCapableBeanFactory#createBean方法中,調用AbstractAutowireCapableBeanFactory#doCreateBean方法,實際執行創建A對象。
- 實例化A對象,給字段賦值默認值後,調用DefaultSingletonBeanRegistry#addSingletonFactory方法,傳入A對象的lamda表達式形式的ObjectFactory對象,將ObjectFactory對象放入三級緩存singletonFactories中,並從2級緩存earlySingletonObjects中移除(雖然這裡沒有),設置A對象已經開始註冊。
- 此處傳入的lamda表達式,內部調用瞭AbstractAutowireCapableBeanFactory#getEarlyBeanReference,此方法用來執行實現瞭SmartInstantiationAwareBeanPostProcessor的後置處理器,比如實現AOP的AbstractAutoProxyCreator
- 然後開始執行A對象的AbstractAutowireCapableBeanFactory#populateBean,進行屬性填充。
- 在進行屬性填充時,發現依賴瞭B對象,執行AbstracBeanFactory.getBean方法,嘗試獲取B對象。參考上面步驟4~9。
- 執行到B對象的屬性填充時,發現依賴瞭A對象,執行AbstracBeanFactory.getBean方法,嘗試獲取A對象。
- 在AbstracBeanFactory.doGetBean方法中執行DefaultSingletonBeanRegistry#getSingleton方法,嘗試從緩存中獲取A對象的單例對象緩存。
- 到一級緩存singletonObjects中找,未找到;
- 到二級緩存earlySingletonObjects中找,未找到;
- 到三級緩存singletonFactories中找,找到瞭,並調用ObjectFactory的getObject方法獲取A對象的引用,ObjectFactory內部調用瞭AbstractAutowireCapableBeanFactory#getEarlyBeanReference,獲取到A的早期對象,將A的早期對象放入二級緩存earlySingletonObjects中,並將三級緩存singletonFactories中A對象移除;
- 這樣拿到A的對象之後,B的屬性填充完畢,B初始化完成,方法return到DefaultSingletonBeanRegistry#getSingleton的重載方法時,調用DefaultSingletonBeanRegistry#addSingleton方法,將B對象放入一級緩存,並將B從二三級緩存中移除(雖然已經沒有瞭)。
- 這樣在return回A的流程,第11步,將A依賴的B屬性填充完整,此時A也填充完畢,初始化完成,方法繼續return到A流程的DefaultSingletonBeanRegistry#getSingleton的重載方法時,調用DefaultSingletonBeanRegistry#addSingleton方法,將A對象放入一級緩存,並將A從二三級緩存中移除(此時隻有二級緩存中有)。
- 這樣A和B就初始化完成瞭。
- 如果A或者B存在AOP,需要返回代理對象,這操作是在第9步的AbstractAutowireCapableBeanFactory#getEarlyBeanReference中完成的,B嘗試獲取A的時候,觸發瞭這個方法,如果A需要被代理,則是在這個方法中執行的,這個方法最終返回瞭一個代理對象,並將這個對象以A的名義放入瞭二級緩存。
- 打完收工。
源碼
AbstractApplicationContext#refresh#finishBeanFactoryInitialization 入口類方法 刷新上下文,初始化BeanFactory,完成工廠初始化。
DefaultListableBeanFactory#preInstantiateSingletons 準備實例化單例對象
@Override public void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } // Iterate over a copy to allow for init methods which in turn register new bean definitions. // While this may not be part of the regular factory bootstrap, it does otherwise work fine. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // Trigger initialization of all non-lazy singleton beans... for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); if (bean instanceof FactoryBean) { FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged( (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } } else { getBean(beanName); } } } // Trigger post-initialization callback for all applicable beans... for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize") .tag("beanName", beanName); SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { smartSingleton.afterSingletonsInstantiated(); return null; }, getAccessControlContext()); } else { smartSingleton.afterSingletonsInstantiated(); } smartInitialize.end(); } } }
AbstractBeanFactory#getBean(java.lang.String) 獲取bean對象
@Override public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); }
AbstractBeanFactory#doGetBean 實際執行獲取bean對象
/** * Return an instance, which may be shared or independent, of the specified bean. * @param name the name of the bean to retrieve * @param requiredType the required type of the bean to retrieve * @param args arguments to use when creating a bean instance using explicit arguments * (only applied when creating a new instance as opposed to retrieving an existing one) * @param typeCheckOnly whether the instance is obtained for a type check, * not for actual use * @return an instance of the bean * @throws BeansException if the bean could not be created */ @SuppressWarnings("unchecked") protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object beanInstance; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (!typeCheckOnly) { markBeanAsCreated(beanName); } StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate") .tag("beanName", name); try { if (requiredType != null) { beanCreation.tag("beanType", requiredType::toString); } RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } } catch (BeansException ex) { beanCreation.tag("exception", ex.getClass().toString()); beanCreation.tag("message", String.valueOf(ex.getMessage())); cleanupAfterBeanCreationFailure(beanName); throw ex; } finally { beanCreation.end(); } } return adaptBeanInstance(name, beanInstance, requiredType); }
AbstractAutowireCapableBeanFactory.java createBean、doCreateBean、getEarlyBeanReference
@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } } protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. 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)); } // 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); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; } protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
DefaultSingletonBeanRegistry.java 三級緩存
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 ConcurrentHashMap<>(16); ... /** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; } }
到此這篇關於Spring三級緩存解決循環依賴 的文章就介紹到這瞭,更多相關Spring 循環依賴內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 聊聊Spring循環依賴三級緩存是否可以減少為二級緩存的情況
- Spring解決循環依賴的方法(三級緩存)
- Java中的Spring 如何處理循環依賴
- Spring源碼剖析之Spring處理循環依賴的問題
- 關於spring循環依賴問題及解決方案