Spring 源碼解析——IOC 源碼解析(單例 Bean 的循環依賴解決方案)(三)

寫文章不易,轉載請標明出處。

同時,如果你喜歡我的文章,請關注我,讓我們一起進步。

一、概述

在上一篇博文中我們已經重點分析了 Bean 的實例化和初始化的主體流程,在這篇博文中我們會在其基礎上進一步探究 Bean 的初始化過程,並通過源碼來分析在 Spring 中是怎樣解決 Bean 循環依賴問題的。

但是在這裏我們需要明確的一點是我們在這裏所分析的是 Bean 的作用範圍爲單例(Singleton)時的情況,當 Bean 的作用範圍爲原型(Protype)時是採用其他的解決方案的,這點是需要注意的(網上有很多文章連前提都不提就開始講所謂的 Bean 循環依賴的解決方案)。

二、問題描述

我們都知道 Spring 的最大的特色就是它的 IOC 機制(即控制反轉),通過 IOC 機制開發者將 Java Bean 的控制權交給 Spring 容器,由 Spring 容器來完成 Bean 的實例化和初始化工作。但是在這個過程中可能遇到的一種問題就是 Bean 的循環依賴問題,何爲循環依賴,顧名思義就是 Bean 中存在着相互依賴的關係,這種問題主要出現在當一個 Bean 中存在着另一個 Bean 作爲屬性,而另一個 Bean 中又使用了該 Bean 作爲屬性(聽不懂?聽不懂沒關係,繼續往下看)。

通過前面博文的分析,我們知道 Bean 的實例化過程其實總體上可以大致分爲兩個部分,第一個部分就是創建 Bean 實例(實例化),而第二個部分就是初始化 Bean(簡單理解就是爲 Bean 的屬性賦值),並且我們知道 Bean 其實就是一個 Java 對象實例,它也包含了屬性值和方法,而這種循環依賴的問題準確來說就出現在初始化過程中爲屬性賦值時,簡單的可以用下圖來理解。

    // 示例代碼
    public class BeanA {
    	@Autowired
    	private BeanB bean;
    }
    
    public class BeanB {
	@Autowired
	private BeanA bean;
    }

通過示意圖和代碼我們可以大致瞭解到循環依賴的問題產生的原因,表面上來看是因爲我們在一個 BeanA 中注入了一個 BeanB 類型的 Bean 作爲屬性值,而在 BeanB 中又注入了一個 BeanA 類型的 Bean 作爲屬性值,這看上去是一個循環(其實這裏還可以有更多的對象來產生這種依賴關係),但是當你不理解 Bean 的生命週期時,你會覺得這好像沒有什麼大的問題啊,Spring 完全可以先實例化一個 BeanA 然後再實例化一個 BeanB(實例化過程中都不對其屬性進行賦值),然後都實例化完成後再對它們互相賦值就可以了啊,這好像沒問題啊,但是如果你瞭解了 Bean 的實例化過程你可能就不會這麼想了(先祭出 Bean 實例化簡化版流程圖,未包含解決循環依賴方案)。

 之前我們分析過,在 Bean 的實例化(創建實例)完成後,Spring 會立即對它的屬性進行賦值操作(屬性注入),這時它首先會嘗試從緩存池中獲取 Bean 實例,如果獲取失敗它就會嘗試首先去實例那個依賴的 Bean 實例。舉個例子,還是上面的 BeanA 和 BeanB,當 Spring 開始實例化時,首先它會將 BeanA 和 BeanB 的 BeanDefinition 註冊到 BeanFactory 的 BeanDefinitionMap 中,然後假設這裏先開始實例化 BeanA,當 Spring 創建完 BeanA 後,它發現 BeanA 存在屬性 bean,因此它開始嘗試爲 BeanA 的屬性 bean 賦值,這時它發現 bean 的類型爲 BeanB,所以它會先去到緩存池中獲取 BeanB 實例,但是因爲此時 BeanB 還沒有被實例化所以獲取失敗,然後它就會去嘗試先實例化 BeanB,但是當它完成 BeanB 的創建,開始對 BeanB 進行賦值的時候,又發現 BeanB 存在一個類型爲 BeanA 的屬性 bean,所以按照剛剛的邏輯,它又會去緩存池中嘗試獲取 BeanA 實例,但是因爲此時 BeanA 僅僅被創建成功,還沒有完成實例化(沒有完成屬性注入),所以它還沒有被放入到緩存池中,那麼 Spring 就又開始嘗試實例化 BeanA,然後當它創建完 BeanA 將要對其進行賦值的時候,又發現它的一個屬性類型爲 BeanB,所以他又去實例化 BeanB,最後陷入了無解的死循環(大致流程如下圖)。

三、解決思路分析

我們可以先嚐試着自己去思考一下,如果我們開發的框架中遇到了這種問題,我們會怎麼去解決它。首先,分析出現問題的根本原因就是當 BeanA 要去注入 BeanB 時,在實例化 BeanB 的過程中又要去注入 BeanA,但此時存在的問題就是因爲我們的 BeanA 還沒有實例化完成,所以沒有被添加到單例緩存池中,因此也就沒有辦法在實例化 BeanB 的過程中被獲取到,因而導致最後在爲 BeanB 進行屬性注入時又不得不去嘗試先去實例化 BeanA。

但是我們仔細思考一下,當我們在爲 BeanB 進行屬性注入的時候,這個時候 BeanA 真的是完全不可用的麼?我們知道 Bean 的實例化分爲兩個階段,即創建 Bean 對象,然後爲 Bean 對象賦值,這也就是說其實當我們在爲 BeanB 進行屬性注入的時候 BeanA 已經被創建出來了,只是還沒有進行屬性的賦值操作,換句話說這個時候的 BeanA 其實已經是一個可用的實例對象了,只不過屬性值還爲空而已,但這又有什麼關係呢,我們在爲 BeanB 進行屬性注入的時候,需要的只是 BeanA 實例的一個引用而已,我們又不需要它已經被完全實例化好了(屬性已經完成賦值操作)。

因此,我們其實可以有這樣的一個解決思路,就是創建一個集合來單獨保存位於中間狀態(創建完成但未被賦值)的 Bean,我們這裏姑且稱他爲中間態緩存池(Spring 中稱爲 earlySingletonObjects 和 singletonFactories)。然後當我們已經創建好一個 Bean 實例後就將它放到這個緩存池中,這樣當我們後續再爲 BeanB 進行屬性注入時先從單例緩存池中獲取,獲取失敗後再嘗試從中間緩存池中獲取,而因爲此時 BeanA 已經創建完成了,所以這裏就一定可以獲取到 BeanA 的實例對象(雖然此時 BeanA 還未完成屬性賦值,但這並不重要),從而就可以正常的完成 BeanB 的屬性賦值操作,也就可以完成 BeanA 的屬性賦值操作,最終也就打破了這個循環引用的問題。

整體思路的流程圖放在下面了,然後我們接着來通過 Spring 的源碼來分析一下 Spring 中的具體代碼實現。

四、源碼解析

在開始進行源碼解析之前先聲明一下,因爲對於 Bean 的實例化和初始化過程在前面的博文中已經詳細的分析過了,因此在這篇博文中不再贅述,並且爲了使分析的思路更加清晰,我會將無關的代碼都刪除掉,只留下與 Spring 解決單例 Bean 循環依賴相關的代碼。對於源碼的解析我們直接從實例化 BeanA 的 getBean 方法開始,示例的代碼如上。

	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false); // name: "beanA"
	}

	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

		// 1.調用 getSingleton 重載版本一獲取 BeanA 實例
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
				// 2.上面獲取失敗因此嘗試使用 getSingleton 重載版本二獲取 BeanA 實例
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
			}
		}

		return (T) bean;
	}

首先 Spring 會通過調用 getBean 方法來獲取 Bean 實例,這個方法實際會調用到 doGetBean 方法,而在這個方法中首先會嘗試使用 getSingleton 重載版本一方法來從單例緩存池中獲取 Bean 實例,當獲取失敗後又經過一系列的邏輯後最終又會調用到 getSingleton 重載版本二方法來創建 Bean 實例。

	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
                        // 3.嘗試從單例緩存池中獲取 BeanA 實例
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
                                // 4.調用 beforeSingletonCreation 方法
				beforeSingletonCreation(beanName);

				try {
                                        // 6.因爲外部入參的 singletonFactory 是一個 Lambda 表達式
                                        // 所以調用其 createBean 方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				finally {
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

	protected void beforeSingletonCreation(String beanName) {
                // 5.將當前 BeanA 的 BeanName(beanA) 添加到 singletonsCurrentlyInCreation 集合中
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

	protected void afterSingletonCreation(String beanName) {
                // 將當前 BeanA 的 BeanName(beanA) 從 singletonsCurrentlyInCreation 集合移除
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

 進入到 getSingleton 重載版本二方法後,它首先會再次嘗試從單例緩存池中獲取 Bean 實例,當獲取失敗後會進入到正式的實例化流程,首先會調用 beforeSingletonCreation 方法,這個方法的代碼我也貼在了下面,這一步就是解決循環依賴的第一個關鍵點,即將當前 Bean 的 BeanName 添加到 singletonsCurrentlyInCreation 集合中,這也就意味着當前 Bean 已經進入到了實例化流程(這一步對應着當在實例化 BeanB 過程中嘗試獲取 BeanA 實例來進行屬性注入時調用 getSingleton 重載版本一方法時,getSingleton 方法裏面的一個判斷方法即 isSingletonsCurrentlyInCreation,如果確認 BeanA 已經在創建過程中了那麼就表示此時已經進入了循環依賴狀態,所以開始嘗試從中間態緩存池中獲取實例化完成但初始化未完成的 BeanA 實例來打破循環依賴)。

之後會調用入參 singletonFactory 的 getObject 方法來獲取 Bean 實例,而這個 singletonFactory 就對應着外部傳進來的 Lambda 表達式中的 createBean 方法,因此首先會調用 createBean 方法來創建一個 Bean 對應的 FactoryBean 對象。

這裏可以稍微提一下的是下面的 afterSingletonCreation 方法對應着上面的 beforeSingletonCreation 方法,這個方法的主要作用就是將 BeanName 從 singletonsCurrentlyInCreation 集合中移除,標誌着當前 Bean 已經完成了實例化。

 

        protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
            try {
                // 7.調用 doCreateBean 方法創建 Bean 實例 
                Object beanInstance = doCreateBean(beanName, mbdToUse, args);
                return beanInstance;
            }
        }

        protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		if (instanceWrapper == null) {
		        // 8.創建 BeanA 實例對象
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}

		// 9.判斷 BeanA 時候爲單例、當前是否允許循環引用
                // 以及當前 BeanA 是否存在於 singletonCurrentlyInCreation 集合中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
                        // 10.因爲 BeanA 符合上述判斷條件因此調用 addSingletonFactory 方法
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		try {
                        // 13.對 BeanA 實例進行屬性注入
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

	}

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
                                // 11.將 BeanA 的 FactoryBean 添加到集合 singletonFactories 中
				this.singletonFactories.put(beanName, singletonFactory);
                                // 12.移除 earlySingletonObjects 中的 BeanA 實例緩存
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

 這裏面的 createBean 方法是一個空殼方法,其直接調用了 doCreateBean 方法,在 doCreateBean 方法中首先調用了 createBeanInstance 方法來創建 Bean 實例對象,然後計算了一個判斷表達式賦值給 earlySingletonExposure,對於這個判斷表達式一共三個條件,首先當前被實例化的 Bean 一定是一個單例 Bean,並且當前是允許循環引用的,最後判斷當前 Bean 是在 singletonsCurrentlyInCreation 集合中的,因爲我們上面已經將它的 BeanName 添加到集合中了,所以這三條都是滿足的,因此會調用 addSingletonFactory 方法,這個方法就是解決循環依賴問題的第二個關鍵點,在這方法裏面它將當前 Bean 對應的 FactoryBean 添加到了singletonFactories 集合中,然後移除了 earlySingtonObjects 中緩存的該 Bean 實例(此時其實集合中並不存在該實例)。

至於爲什麼在這裏不直接將已經創建的 Bean 實例放到 earlySingtonObjects 集合中,而是先把它的 FactoryBean 放到 singletonFactories 集合中,後面再從這個集合中去獲取 BeanFactory,再通過 getObject 方法來獲取 Bean 實例,我認爲主要的原因可能是因爲在將 Bean 放入到中間態緩存池的過程中其實 Spring 還做了一些其它的工作,調用了一些 BeanPostProcessor,具體代碼我們可以稍微看一下 getEarlyBeanReference 這個方法。

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;
}

在執行完上面這些代碼之後 Bean 實例對象已經被創建成功,但是它得屬性值仍爲空(如下圖),因此就接下來會調用 populateBean 方法來對 Bean 實例進行屬性注入(之後又會調用 initializeBean 方法來調用一些相關的初始化方法,跟本文無關,所以暫時不分析)。 

	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		if (hasInstAwareBpps) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                                        // 14.當 bp 爲 AutowiredAnnotationBeanPostProcessor 時
                                        // 調用它的 postProcessProperties 方法進行 BeanA 屬性自動注入
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
				}
			}
		}
	}

	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
                // 15.獲取注入元數據
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
                        // 16. 對 BeanA 進行屬性注入
			metadata.inject(bean, beanName, pvs);
		}
		catch (BeanCreationException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}

跟進 populateBean 方法,在這裏我直接省略了所有的無關代碼,直接看爲屬性進行賦值的核心代碼,因爲在示例代碼中我們是採用的 @Autowired 註解來進行自動注入的,在 Spring 中對於 @Autowired 自動注入採用的是 BeanPostProcessor 來實現的,也就是我們常說的後置處理器,因爲如果詳細分析 @Autowired 的自動注入過程就該偏離了本文的核心,所以我們這裏不進行分析(後面會有博文專門分析),只需要知道屬性自動注入使用的 BeanPostProcessor 是 AutowiredAnnotationBeanPostProcessor 即可。在這裏調用了它的 postProcessProperties 方法,而在 postProcessProperties 方法中首先獲取了 Bean 的元數據信息(具體的 metadata 信息如下截圖),然後開始對 Bean 實例進行屬性注入。

	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
                                // 17.遍歷獲取所有屬性調用 InjectedElement.inject 進行屬性注入
				element.inject(target, beanName, pvs);
			}
		}
	}

 接下來在 InjectionMetadata 的 inject 方法中又獲取了所有的屬性,然後通過遍歷對每一個屬性調用 InjectedElement 的 inject 方法進行處理。

	protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			try {
                                // 18.屬性解析判斷等操作方法調用鏈
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
                                // 成功獲取到屬性實例對象後通過該方法賦值
				field.set(bean, value);
			}
		}
	}


	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
			if (result == null) {
                                // 18.屬性解析判斷等操作方法調用鏈
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);    
			}
			return result;
	}

	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
		try {
			if (instanceCandidate instanceof Class) {
                                // 18.屬性解析判斷等操作方法調用鏈
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
		}
	}

	public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException {
                // 19.調用 getBean 方法獲取 BeanB 實例
		return beanFactory.getBean(beanName); // beanName: "beanB"
	}

 在進入到 InjectedElement 的 inject 方法後又進行了一系列驗證和方法調用,我們直接略過這些調用(具體的方法調用鏈見下圖),直接來到核心的 resolveCandidate 方法,在這個方法中調用了 getBean 方法來嘗試獲取 BeanB 實例(注意此時我們還在初始化 BeanA 實例的流程中)

 

 通過調用 getBean 方法其實來獲取 BeanB 實例其實就已經回到了最開始的代碼邏輯中(博文開始處也是從 getBean 方法開始分析),因爲我們上面已經完整的分析過 BeanA 的 getBean 流程了,BeanB 的與它的相同,因此我們這裏就不再重述一遍了,直接跳過 BeanB 執行上面的流程,走到這一步驟(此時位於 BeanB 的初始化流程中,它所執行的 resolveCandidate 是爲了獲取 BeanA 實例來注入其屬性。具體可見下面兩張圖,第一張是當 BeanA 執行到這步時嘗試獲取 BeanB 實例來注入屬性,第二張是當 BeanB 執行到這步時嘗試獲取 BeanA 實例來注入屬性,圖三爲)

 接下來就是解決方案中最關鍵的地方了,也就是當 BeanB 實例創建完成後,在爲 BeanB 進行屬性注入 BeanA 實例時,會通過調用 getBean 方法來獲取 BeanA 的實例,但 getBean 方法是一個空殼方法,因此最終會調用到 doGetBean 方法,此時通過下圖我們可以看到 doGetBean 方法中會通過 getSingleton 重載版本一方法來獲取 BeanA 實例。

 進入到 getSingleton 重載版本一方法中,這次它也會先嚐試從單例緩存池中獲取 BeanA 實例,但因爲此時 BeanA 的實例化過程還沒有完成(實例創建完成但未完成賦值),因此這時仍然獲取不到 BeanA 實例,然後它就會去判斷此時我們要獲取的 Bean 是否位於 singletonsCurrentlyInCreation 集合中(換句話說就是判斷此時我們要獲取的 Bean 是不是正在實例化過程中,此時是不是正處於循環引用狀態),因爲我們前面在 BeanA 執行 getSingleton 的 beforeSingletonCreation 方法時已經將 BeanA 對應的 BeanName 添加到 singletonsCurrentlyInCreation 中了,所以這裏的判斷一定是成立的,然後它首先會嘗試從 earlySingletonObjects 集合中獲取中間態 BeanA 實例,但因爲此時中間態實例還未被加入到 earlySingletonObjects 中,因此接着它就會嘗試去從 singletonFactories 集合中去獲取 BeanA 所對應的 FactoryBean,當其成功獲取到 FactoryBean 之後,就可以通過 FactoryBean 的 getObject 方法來獲取到中間態的 BeanA 實例(創建完成但屬性未被賦值),接着會將 FactoryBean 從集合中移除並將剛剛獲取到的中間態 BeanA 實例添加到 earlySingletonObjects 集合中(這樣如果還存在更多的循環依賴,下次再獲取中間態 BeanA 實例就不需要通過 FactoryBean 了,而是可以直接從 earlySingletonObjects 集合中獲取),最後再將中間態 BeanA 實例返回。這樣就完美的解決了循環依賴的問題。

 當重載版本一的 getSingleton 方法成功獲取到中間態 BeanA 實例後就不會再去執行重載版本二的 getSingleton 方法,而是會直接將獲取到的中間態 BeanA 實例返回,因此之前方法調用會一直將這個中間態 BeanA 實例返回,最終返回到 BeanB 的 InjectedElement 的 inject 方法,通過 field.set 方法完成了 BeanB 中 bean 屬性(類型爲 BeanA)的賦值操作 ,最後將已經初始化完成的 BeanB 實例保存到單例緩存池中並同時將其返回給 BeanA 的 InjectedElement 的 inject 方法,再通過 field.set 方法完成了 BeanA 中 bean 屬性 (類型爲 BeanB)的賦值操作,最終將實例化完成的 BeanA 實例添加到單例緩存池中並返回,這樣就完成了 BeanA 和 BeanB 的實例化工作。

	protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			try {
                                // 屬性解析判斷等操作方法調用鏈
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
                                // 成功獲取到屬性實例對象後通過該方法賦值
				field.set(bean, value);
			}
		}
	}

五、內容總結

 這篇博文到這裏就結束了,如果你還有什麼疑問可以評論在下面,我們一起來討論。最後的內容總結我就把整體的一個方法調用流程圖放在這裏了,這個流程圖可能有的地方不是特別嚴謹,主要是沒有詳細區分是層級調用還是平級調用,但畢竟我們主要在意的是方法的整個調用流程,所以總的來說應該也還是比較好理解的,等以後有時間可能會補一些新的更加詳細的流程圖。

生活是公平的,哪怕吃了很多苦,只要你堅持下去,一定會有收穫,即使最後失敗了,你也獲得了別人不具備的經歷。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章