SpringBoot成長記10:Bean例項化的流程和設計

SpringBoot成長記10:Bean例項化的流程和設計

之前我們已經分析SpringBoot在run方法時,它會執行的refresh()容器的操作。

SpringBoot成長記10:Bean例項化的流程和設計

在SpringBoot中,refresh()有十幾個方法,每個方法的大重要程度是不一樣的,我們透過抓大放小的方式,分析處理上圖3個核心邏輯。

並且已經研究完了invokeBeanFactoryPostProcessors和onRefresh的邏輯,分析它們的原理和設計思想。

之前主要分析的:

原理有對SpringBoot的自動裝配配置如何做到的、第三方技術如何進行擴充套件的、tomcat如何啟動的

設計思想有SpringBoot擴充套件介面設計、有對Tomcat元件的擴充套件設計、Spring容器抽象思想的設計、SpringBoot和第三方技術整合的擴充套件設計等等。

refresh()還有一個非常關鍵的操作,就是bean的例項化,今天我們就來看下refresh最後一個方法—finishBeanFactoryInitialization。

看看它如何執行Bean例項化的流程和設計的。

finishBeanFactoryInitialization之前和之後的操作概況

SpringBoot成長記10:Bean例項化的流程和設計

可以看到,bean的例項化前後,還是做了一些事情的,主要執行的是一些擴充套件點,比如listener的擴充套件點執行、LifycycleProcessor的執行。

這一節我們核心關係的是bean建立流程和設計,所以我們抓大放小,過就可以。直接來看下面建立bean吧。

preInstantiateSingletons方法的核心脈絡

其實bean的例項化大家或多或少都知道一些。所以我不會特別詳細的每一個方法都帶大家看。

我們還是本著先脈絡後細節,最後思考的思想來分析Bean的例項化,當你用這種方法分析玩後,和你之前分析對吧下有什麼區別,可以感受下。

如果之後大家有訴求需要精讀Bean例項化的邏輯,我之後可以考慮放在Spring成長記中為大家仔細帶來Bean例項化的分析。

這裡我們主要過下它的核心原始碼就可以了。

由於SpringBoot是為了更好的使用Spring,它是基於Spring的。如果你懂Spring例項化,這塊其實非常好理解的。

讓我們來看下吧!

finishBeanFactoryInitialization主要的程式碼如下所示:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // Initialize conversion service for this context。 if (beanFactory。containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory。isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService。class)) { beanFactory。setConversionService( beanFactory。getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService。class)); } // Register a default embedded value resolver if no bean post-processor // (such as a PropertyPlaceholderConfigurer bean) registered any before: // at this point, primarily for resolution in annotation attribute values。 if (!beanFactory。hasEmbeddedValueResolver()) { beanFactory。addEmbeddedValueResolver(strVal -> getEnvironment()。resolvePlaceholders(strVal)); } // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early。 String[] weaverAwareNames = beanFactory。getBeanNamesForType(LoadTimeWeaverAware。class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } // Stop using the temporary ClassLoader for type matching。 beanFactory。setTempClassLoader(null); // Allow for caching all bean definition metadata, not expecting further changes。 beanFactory。freezeConfiguration(); // Instantiate all remaining (non-lazy-init) singletons。 beanFactory。preInstantiateSingletons(); }

這個方法的脈絡其實比較清楚,其實最關鍵的只有一句話:

// Instantiate all remaining (non-lazy-init) singletons。beanFactory。preInstantiateSingletons();

其餘的都是給beanFactory補充點東西而已,不是很關鍵。

這句話從註釋很清楚的說了,是根據BeanDefinition例項化所有剩餘的單例非延遲初始化的bean。

整個方法我透過先脈絡的思想,給大家概括了下:

public void preInstantiateSingletons() throws BeansException { List beanNames = new ArrayList<>(this。beanDefinitionNames); //遍歷所有beanDefinition,基於beanDefinition建立bean for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (isFactoryBean(beanName)) { //做一些處理FactoryBean //再從容器中獲取bean,如果不存在就建立 getBean(beanName); }else{ //從容器中獲取bean,如果不存在就建立 getBean(beanName); } } // 執行Bean的擴充套件操作 for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { smartSingleton。afterSingletonsInstantiated(); } } }

上面的程式碼是我對原始碼精簡後的邏輯,它的脈絡非常清晰了整體如下圖所示:

SpringBoot成長記10:Bean例項化的流程和設計

建立bean的核心流程

當你知道了preInstantiateSingletons方法的核心脈絡後,它主要觸發的是getBean方法,之後觸發了doGetBean。

doGetBean整個方法還是比較複雜的,我還是透過先脈絡的思想,抓大放小後,給大家精簡了下原始碼,精簡後如下:

@Overrid public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; //嘗試獲取bean,如果容器中有了,就不需要建立了 Object bean = getSingleton(beanName); if(bean == nul){ //getFromParentBeanfacotry 當前容器沒有bean對應的單例物件,嘗試從父容器獲取,如果父容器為空,則不做處理 //預設父容器空,這裡略過 //當前bean的依賴dependsOn處理,遞迴呼叫getBean final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); String[] dependsOn = mbd。getDependsOn(); if (dependsOn != null) { getBean(dep); } // 單例還是多例的方式建立bean if (mbd。isSingleton()) { bean = createBean(beanName, mbd, args); }else if (mbd。isPrototype()) { bean = createBean(beanName, mbd, args); }else { String scopeName = mbd。getScope(); final Scope scope = this。scopes。get(scopeName); bean = createBean(beanName, mbd, args); } } //bean的擴充套件,使用轉換器處理bean T convertedBean = getTypeConverter()。convertIfNecessary(bean, requiredType); return (T) bean;}

doGetBean整體程式碼方法透過,抓大放小,分析脈絡後,其實已經很清晰了。主要做了這麼幾件事:

1)getSingleton從容器Beanfactory中的map屬性,獲取bean,如果非空,直接就可以使用

2)如果容器中沒有這個bean,透過判斷是否單例,來執行對應的建立bean方法createBean

3)如果當前bean的依賴dependsOn處理,遞迴呼叫getBean

4)最後執行了bean的擴充套件,使用轉換器處理bean

doGetBean方法的大體脈絡,基本上就是這四步,如下圖所示:

SpringBoot成長記10:Bean例項化的流程和設計

真正建立bean的邏輯,到現在我們還是沒有看到,需要繼續向下找。doGetBean之後下面就會執行createBean

同理我們使用之前的方法繼續梳理脈絡、抓大放小得到如下程式碼:

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {、 //Give BeanPostProcessors a chance to return a proxy instead of the target bean instance。 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } Object beanInstance = doCreateBean(beanName, mbdToUse, args); return beanInstance;}

這裡createBean其實主要執行了resolveBeforeInstantiation和doCreateBean方法。

1)resolveBeforeInstantiation方法從註解上看,是給動態代理建立一個物件的機會,也就說,可以透過BeanPostProcessor使用動態代理對某些bean直接進行建立。這個非常有意思,也很關鍵,你想想是不是有的技術就是利用這裡進行建立的呢?

2)如果不滿足第一個條件,就會使用doCreateBean來建立Bean

整個邏輯如下圖所示:

SpringBoot成長記10:Bean例項化的流程和設計

這裡終於找到了一種bean建立的方式了,之後應該還有其他方式,比如反射。我們繼續來看doCreateBean。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; //建立bean,bean的例項化 if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } //bean屬性的填充 populateBean(beanName, mbd, instanceWrapper); //bean 擴充套件點的觸發 initializeBean(beanName, exposedObject, mbd); //bean 擴充套件點的新增 registerDisposableBeanIfNecessary(beanName, bean, mbd); return exposedObject;}

整個doCreateBean方法,透過我們之前的思路,一樣精簡完後,脈絡也很清楚。主要有:

1)建立bean,bean的例項化

2)bean屬性的處理

3)bean 擴充套件點的觸發

4)bean 擴充套件點的新增

doCreateBean的脈絡如下圖所示:

SpringBoot成長記10:Bean例項化的流程和設計

這裡可以看到,除了之前動態代理的截胡,終於找到了bean例項化,建立bean的地方了。

其餘對bean屬性處理和擴充套件點,我們先不看。重點研究清楚bean的建立再說。

createBeanInstance同樣被我們抓大放小後的程式碼如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point。 Class<?> beanClass = resolveBeanClass(mbd, beanName); //instanceSupplier方式建立bean Supplier<?> instanceSupplier = mbd。getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } //FactoryMethod方式建立bean if (mbd。getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } //反射的方式建立bean if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } } return instantiateBean(beanName, mbd);}

你會發現有多種建立的bean的方式,並不是只有反射,方式主要有:

1)instanceSupplier方式建立bean

2)FactoryMethod方式建立bean

3)反射的方式建立bean

那麼再算上之前動態代理建立bean、Factorybean建立的bean。一共已經有5種可以建立bean的方式, 但是一般我們還都是透過反射建立的居多。

你可能沒有見過其他的方式建立的bean,但是我在一些技術中也見過一些,給大家分享下:

比如

Shiro框架使用Factorybean建立的就比較多

動態代理建立bean、dubbo就有很多這麼建立的

FactoryMethod建立的bean,SpringBoot自動裝配的時候有時候會用到,之前tomcat啟動的時候,TomcatServletWebServerFactory是不是就用到了。

instanceSupplier是Spring5之後才有的,目前我還沒有見到啥框架用到過....

好了不管如何,最終你獲得瞭如下的一張圖,總結bean的建立方式:

SpringBoot成長記10:Bean例項化的流程和設計

到這裡bean 的建立的流程,我們就大體分析完了,由於我們不是分析Spring,所以就不會再對這裡面的每個細節進行分析了。

之後有機會出Spring成長記的時候,我可以在帶大家詳細分析吧。

只是熟悉SpringBoot中,Spring例項化bean的流程瞭解到這裡基本就可以了。

建立的bean整個流程可以總結下圖的幾步:

SpringBoot成長記10:Bean例項化的流程和設計

Bean例項化的擴充套件點設計

最後我們來看下Bean擴充套件設計吧,這個其實網上都有一大堆了,但是你一定要注意,你得會區分優劣的文章、提煉關鍵點,要對這些有自己的思考才行。

就像我現在給大家分享的,就是我對擴充套件點的思考。這個是我一直給大家強調的。

好了,我來簡單說下,我對Bean的擴充套件點設計的思考和理解吧。

在Spring中,Bean例項化的時候,有很多擴充套件點,這些擴充套件點其實還是很關鍵的。

比如:在Spring的生態系統中,很多技術都是透過Bean的擴充套件點來實現的。而且包括第三方的技術,比如bytetcc分散式事物框架的實現原理和Bean擴充套件點BeanPostProcessor就有很大的關係、大企業自研框架,可以實現自定義註解的處理、自定義配置檔案的處理、給自己開發的bean設定屬性等等。

那Bean的擴充套件點設計了哪些呢?我給大家花了一個圖,基本就能概況常見的擴充套件點了,當然可能還有一些其他的擴充套件點,不管有多少個,它們都是擴充套件點,合理利用就好了 ,這個是關鍵。

Bean例項化時,常見的擴充套件點的設計如圖所示:

SpringBoot成長記10:Bean例項化的流程和設計

小結

如果你去看Bean的例項化的整個流程,其實其中的細節很複雜的,如果在複雜中找到關鍵點,是SpringBoot成長記以來,一直想要教給大家的。

最後透過對Bean例項化的分析,讓大家熟練的應用了之前的學到的先脈絡後細節、抓大放小、連蒙帶猜、畫核心元件圖、流程圖、看註釋等思想和方法。

而且每看一陣子邏輯,要對它做出思考,思考它的設計,它的擴充套件、它的思想理念等等。

這個是常用的一套方法論,可能不適合所有場景,但是大多情況可以讓你閱讀原始碼或者研究技術原理的時候,不那麼不安,不會覺得它們太難,可以讓你有方向、有方法。

今天的內容,其實沒有什麼要總結的,比較重要的就是最後bean 的擴充套件設計,我就不在重複了。

好了,我們下一節再見!

本文由部落格一文多發平臺 OpenWrite 釋出!