@java
關於Spring 循環引用 網上的分析文章不少,寫的水平參差不齊,雖然看完了 知道怎麼個回事 可是過段時間仍是忘記了,主要本人沒過目不忘的本領哈,可是隻要記住主要的點就行了spring
可是若是你本身想更深刻的瞭解,仍是要本身去看源碼分析一波,由於別人分析的時候,有些知識點你是get不到的,只有當本身走進源碼去看的時候,纔有get到更多的!好比網上不少文章都分析Springs是怎麼解決循環依賴的 可是爲何只有單類的才能夠,Prototype的就不行呢,在哪裏不行,或者說構造器的注入爲何也不能夠,最後若是解決循環依賴,或者說 怎麼去換中寫法去解決問題。緩存
紙上得來終覺淺 絕知此事要躬行! 這句話獻給正在讀文章的你,看完記得點贊,還有就是本身去下載Spring 源碼 去看看app
OK,進入正文,固然上面也不是廢話啦,Spring 的循環引用 我想讀者們應該知道,不知道的話,算了 來個code把!ide
@Component public class CycleTestServiceA { private CycleTestServiceB b; public void setB(CycleTestServiceB b) { this.b = b; } } @Component public class CycleTestServiceB { private CycleTestServiceA a; public void setA(CycleTestServiceA a) { this.a = a; } }
上面的 代碼 就是一個普通的set注入的方式,A裏面依賴B,B裏面依賴A,這樣就致使了循環依賴,Component默認是Singleton的函數
咱們從Spring Beanc建立開始做爲入口,在Spring IoC 容器中一個完整的Bean 要進過實例化 和初始化的階段源碼分析
Spring Bean 實例化就getBean的過程ui
那咱們接進入源碼去看下getBean的過程this
getBean方法時 BeanFactory 接口的方法 他的實現類有不少,咱們跟進去他的抽象實現類org/springframework/beans/factory/support/AbstractBeanFactory.java 類,其實都是調用了doGetBean方法prototype
下面是我截取的核心代碼
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; /* * 檢測是否 有緩存對象 這個方法時處理循環依賴的關鍵入口 * 記住這個的代碼 我還會回來的 * */ Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isDebugEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); } } bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { /* *Prototype bean 是否在建立當中 若是存在 說明產生了循環依賴 處理Bean 循環依賴的地方 *這個地方就是爲何Scope 是Prototype的時候 會報循環依賴的錯誤,慢慢看 後面會解釋 * */ if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } ... if (!typeCheckOnly) { markBeanAsCreated(beanName);//這個方法就是把當前的bean 加入到alreadyCreated的set集合中 後面有些判斷須要 } try { ... /* * 獲取Bean 的依賴項 這邊的依賴 是咱們在xml 有時候能夠配置的depends-on的依賴 和咱們本次講的循環依賴不是同一個 * 我特別說明下 * */ String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { //註冊依賴 建立Bean 等 } } /* * 若是是單列 建立createBean 記住這個的代碼 我還會回來的 * */ if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { ... } } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } /* * Prototype對象 * */ 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); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } /* * 不是Singleton也不是Prototype,多是自定義scope的對象 * */ else { ... } } } ... return (T) bean; }
上面是dogetBean()的核心方法
帶着這個問題 咱們能夠從上面的代碼中 看下 Spring在處理麼Prototype的時候 有2個方法beforePrototypeCreation(),afterPrototypeCreation(),
上下代碼
/** Names of beans that are currently in creation */ private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<Object>("Prototype beans currently in creation"); protected void beforePrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal == null) { this.prototypesCurrentlyInCreation.set(beanName); } else if (curVal instanceof String) { Set<String> beanNameSet = new HashSet<String>(2); beanNameSet.add((String) curVal); beanNameSet.add(beanName); this.prototypesCurrentlyInCreation.set(beanNameSet); } else { Set<String> beanNameSet = (Set<String>) curVal; beanNameSet.add(beanName); } } protected void afterPrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal instanceof String) { this.prototypesCurrentlyInCreation.remove(); } else if (curVal instanceof Set) { Set<String> beanNameSet = (Set<String>) curVal; beanNameSet.remove(beanName); if (beanNameSet.isEmpty()) { this.prototypesCurrentlyInCreation.remove(); } } }
上面的代碼 我相信小夥伴都能看的懂,就是用一個set集合存儲當前正在建立的Bean的BeanName,並且是用ThreadLocal去存儲Set集合的 ThreadLocal是每一個線程私有的。看到這個 咱們再把目光往代碼上面看一看 isPrototypeCurrentlyInCreation這個方法的判斷
protected boolean isPrototypeCurrentlyInCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName)))); }
看到了麼 這邊就是用這個ThreadLocal裏面的set集合去判斷的,爲何用ThreadLocal想下,你想呀,A依賴B,而B依賴A,AB都是Prototype的,A建立的時候 A會加入到這個set集合中,而後A去填充實例的時候,由於要依賴B,因此去getB,發現B又依賴A,這個時候有要getA,你看 當執行到 最上面的判斷isPrototypeCurrentlyInCreation的時候,是不報了循環引用的錯,由於A已經在prototypesCurrentlyInCreation的Set集合中了,由於整個流程必定是一個線程走下去的,因此存入ThreadLocal中,一點問題沒有,並且還不受其餘線程影響~
不論是哪一種Scope 都是要調用createBean方法的,咱們跟進去代碼 發現惟一重寫的實如今org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java 中
咱們進入代碼看下
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException { RootBeanDefinition mbdToUse = mbd; ... try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. // 該函數的做用是給 BeanPostProcessors 後置處理器返回一個代理對象的機會 // 這裏是實現AOP處理的重要地方 // AOP是經過BeanPostProcessor機制實現的,而接口InstantiationAwareBeanPostProcessor是實現代理的重點 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } ... /* * 後置處理器 沒有返回有效的bean 就建立 * */ Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isDebugEnabled()) { logger.debug("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; }
這邊 我看到一句英文註釋,都沒捨得替換中文,Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. 哈哈 給後置處理器一個返回代理bean的機會,這邊就是Spring 中實現AOP的重點,動態代理 其實就是使用後置處理器 替換了target Bean 的實例,從而達到代理的做用,這個之後聊到AOP 的時候在慢慢聊吧!這個最核心的代碼還在再doCreateBean中,繼續跟進
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null;//BeanWrapper 是Bean 的包裝類 方便對Bean 實例的操做 if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); mbd.resolvedTargetType = beanType; .... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));//知足三個條件 單列 運行循環引用 bean 是否正在建立中 if (earlySingletonExposure) { addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean);//提早暴露引用 獲取早期的引用 } }); } // Initialize the bean instance. 初始化Bean 實例 Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper);//填充Bean if (exposedObject != null) { exposedObject = initializeBean(beanName, exposedObject, mbd);//執行初始化Bean裏面的方法 } } ... if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { /* *這邊其實仍是作了一個判斷,exposedObject是通過了 initializeBean方法方法的 *而bean仍是那個提早暴露的Bean, *爲何要作這個判斷你,是由於exposedObject通過了initializeBean裏面的後置處理器的修改 可能Object 已經改變了 **/ if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } /* *有興趣的能夠根據到上面的每個方法看下 ,這邊就是判斷若是提早暴露的bean已經和在後置處理器裏面修改了而且不同了,就拋出異常,由於提早暴露的Bean 可能做爲了另外的bean的依賴 這樣就會致使單類的bean在容器中有2個實例的出現,這是非法的! */ if (!actualDependentBeans.isEmpty()) { //拋出一個異常 因爲不少文字我就刪掉了 } } } } ... return exposedObject; }
earlySingletonExposure這個主要關注的是earlySingletonExposure 這邊的代碼,這個就是在Bean 實例化完成後,開始填充屬性之間發的代碼
earlySingletonExposure爲true 要知足三個條件
前面2個能夠理解 那最後一個又是什麼呢?話很少說 進入方法看下
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16)); public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); }
這個方法 一看很簡答 就是判斷當前的bean是否在singletonsCurrentlyInCreation的set集合中 那這個集合又是何時加入的呢?帶着這個想法 我又重頭掃描了一篇代碼 還記的org/springframework/beans/factory/support/AbstractBeanFactory.java代碼中的doGetBean()方法裏面Singleton的Bean 在建立Instance的時候是調用了getSingleton方法麼,不清楚的話 能夠往上看下
這個方法 是 addSingletonFactory 方法 構建ObjectFactory的參數的時候 裏面返回使用方法
看下代碼:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); if (exposedObject == null) { return null; } } } } return exposedObject; }
裏面最主要的就是看下getEarlyBeanReference方法 這個方法時SmartInstantiationAwareBeanPostProcessor裏面的方法,他的實現有2個 一個是org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java 還有一個是動態代理使用的 我就不列舉了,
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { return bean; }
看了下 其實InstantiationAwareBeanPostProcessorAdapter的重寫就是 返回了當前的bean 沒有作任何操做。這邊其實就是作了一個引用的保存。
代碼位於org/springframework/beans/factory/support/DefaultListableBeanFactory.java中
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { ... beforeSingletonCreation(beanName); boolean newSingleton = false; ... try { singletonObject = singletonFactory.getObject(); newSingleton = true; } finally { afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return (singletonObject != NULL_OBJECT ? singletonObject : null); } }
這個方法其所就是SingletonBean的核心建立流程
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
當我看到這個singletonsCurrentlyInCreation.add的時候 我很欣慰 由於我以前的問題解決了 就是這個方法把Bean 放入到以前的singletonsCurrentlyInCreation的集合中的
這個應該都很清楚了 就是咱們方法傳入的匿名的ObjectFactory對象,當以前getObject的時候 纔會執行咱們剛纔的看的createBean方法
protected void afterSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); } }
看下afterSingletonCreation方法裏面的東西也很簡單,就是從singletonsCurrentlyInCreation集合中移除
先看下代碼
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
看到 這邊 就不得表介紹下三級緩存了
說道這裏 咱們就清楚了 這個方法其所就是在二級緩存和三級緩存中刪除當前的Bean,把當前的Bean 放入到一級緩存中,由於到了這一步 bean 的實例化,屬性填充,後置處理器執行,初始化等方法都已經執行了。
這個方法 哪裏用的呢 那咱們有要回到上面的代碼doCreateBean中 當earlySingletonExposure爲true的時候 會調用這個方法addSingletonFactory
這個方法就是 當前的Bean能夠提早引用的話執行的方法
看下代碼也很簡答
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); } } }
看下這個方法 說白了就是往三級緩存裏面存放bean的ObjectFactory對象 這個地方也是處理循環引用的關鍵,這個時候Bean 剛剛進行了實例化 尚未進行bean的屬性填充和初始化等一些列方法
那怎麼去解決提早引用的呢?能夠看下ObjectFactory返回的是getEarlyBeanReference對象
這個方法是在doGetBean方法中 從緩存中獲取Bean 對象的方法 這個方法很關鍵 是處理循環依賴的入口,那咱們跟進去看下方法
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 != NULL_OBJECT ? singletonObject : null); }
最終調用的方法如上,allowEarlyReference是爲true的,咱們仍是用最上面的ServiceA和ServiceB 爲例,第一次ServiceA 進入的時候 是無法進入下面的判斷的 應爲當前ServiceA不在SingletonCurrentlyInCreation中,可是當第二次進來,第二次是何時呢,就是在填充ServiceB的時候 須要依賴 ServiceB,這個時候ServiceB也要執行getBean的流程,發現又依賴ServiceA,這個時候 ServiceA就是在SingletonCurrentlyInCreation的集合中了,並且在三級緩存中,這個時候會進行判斷條件裏面的方法,先找一級緩存,找不到就找二級緩存,最後找三級緩存,而後將取出三級緩存裏面的ObjectFactory執行getObject方法 就是獲取咱們上面提到的提早引用的bean,最後將bean 放入到二級緩存,從三級緩存中移除~
看完了 上面的一推 也許很懵逼,可能也是我文字組織能力差,只能之後慢慢改變
上面涉及到幾個緩存 我在邊在重寫描述一下
名稱 | 類型 | 使用說明 | 所屬類 |
---|---|---|---|
singletonObjects | Map<String, Object> | 實例化 初始化都完成的bean的緩存 | DefaultSingletonBeanRegistry.java |
earlySingletonObjects | Map<String, Object> | 可提早引用的 Bean 緩存,這裏面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean | DefaultSingletonBeanRegistry.java |
singletonFactories | Map<String, ObjectFactory<?>> | 單類bean的建立工廠函數對象 | DefaultSingletonBeanRegistry.java |
singletonsCurrentlyInCreation | Set
|
立刻要建立的單類Bean的集合 | DefaultSingletonBeanRegistry.java |
prototypesCurrentlyInCreation | ThreadLocal | object 是一個Set
|
AbstractBeanFactory.java |
alreadyCreated | Set
|
至少建立過一次的Bean 在提早暴露的bean修改了致使不一致時 判斷會用到 | AbstractBeanFactory.java |
最終我仍是用一個方法執行的流程圖 來描述下 循環依賴的處理
那麼爲何構造器的注入方式不行呢?緣由是由於 Bean在實例化階段的時候createBeanInstance的時候就會去建立依賴的B,這樣的話A根本就走不到提早暴露的代碼塊,因此會報一個循環引用的錯誤,報錯的地方就是構造函數參數bean 建立的地方,本身能夠寫個demo,調試下 在哪一步報錯,博主但是看了半天 才找到,哈哈!
關於若是解決構造器的循環注入
https://www.baeldung.com/circular-dependencies-in-spring
這是一篇外國博文,小夥伴們能夠看下
Spring 處理循環依賴的核心就是 三級緩存,讓Bean 提早暴露出來,能夠提早引用,讓互相依賴的Bean 能夠流程上執行下去,從而解決了循環依賴的問題
最後的最後 仍是本身對照源碼 本身理解一遍,我相信必定會加深你的理解,必定會有收穫
碼字不易,花了一個週末的時間,各位看官喜歡的話點個贊,鼓勵下博主,繼續創造,多謝~