從Spring中學到的【1】--讀懂繼承鏈

2022-09-14 06:02:07

最近看了一些 Spring 原始碼,發現原始碼分析的文章很多,而底層思想分析的文章比較少,這個系列文章準備總結一下Spring中給我的啟示,包括設計模式思想、SOLID設計原則等,涉及一些程式設計的基本原則,雖然看似簡單,實則「小道理、大學問」。
我儘量遇到的問題談起,再說解決方案,同時至少舉兩個例子。
這些方法都是基於我遇到的一些實際程式碼,掌握了基本思想,就可以舉一反三。

讓人頭暈眼花的跳轉

如果你通過某些培訓機構的原始碼課,就會發現他們的老師在講原始碼的時候在類之間、方法之間不停地跳,學員一臉懵逼。因為如果不理解老師講課的思路,或者是稍微走一下神,就會覺得自己跟不上了。

其實,問題就在於需要理解原始碼的基本流程和繼承鏈的這種單一職責原則。

讀懂繼承鏈

物件導向的特點是封裝繼承和多型,封裝體現在私有方法。

在Spring中,常見的模式是頂層為介面,然後是抽象類,最後是各個實現類。

介面負責對外暴露,符合面向介面程式設計的準則,在更改實現類時,可以減小對於程式碼的改動,保證了程式碼依賴於抽象(介面)。.

抽象類一般使用模板模式,實現了邏輯的組裝,把子類中的公用邏輯抽取出來,方便子類編寫;模板方法一般為固定的執行鏈,我們讀原始碼時,可以予以關注。比如常見的AbstractApplicationContext::refresh 方法,AbstractBeanFactory::doGetBean方法。由於介面一般只能暴露方法宣告,抽象類可以實現一些狀態的getter,settter,這樣子類存取這些狀態資料只需要呼叫方法即可。

實現類通常有多個,比如todo。有些時候當實現類只有一個或者有一個預設實現類時,常常使用default命名,比如DefaultListableBeanFactory. 讀原始碼時可以選擇地閱讀預設實現。

Java 只支援單繼承,這種語言層面的規範方便了我們閱讀原始碼,一個方法的實現必然在一條繼承鏈中。

舉例1:

Spring中BeanFactory的預設實現是DefaultListableBeanFactory, 通過繼承圖可以看出,有多個抽象類,從上到下分別實現了以下的能力:

  • 別名註冊(SimpleAliasRegistry
  • 單例註冊(DefaultSingletonBeanRegistry
  • FactoryBean 註冊(FactoryBeanRegistrySupport
  • bean工廠介面(主要就是getBean相關的一些方法)(AbstractBeanFactory
  • 自動裝配(AbstractAutowireCapableBeanFactory

同時我們還可以看到,基本上每一個抽象類都對應一個實現的介面:

AliasRegistrySimpleAliasRegistry

SingletonBeanRegistryDefaultSingletonBeanRegistry

AbstractBeanFactoryConfigurableBeanFactory

AbstractAutowireCapableBeanFactoryAutowireCapableBeanFactory

注意到 DefaultListableBeanFactory 實現了ConfigurableListableBeanFactory,這個介面實現了一些簡化設定 beanFactory 的方法,是一個常用的基礎設施類(介面)。

實際上,這個設計也是不得已而為之。Java只支援單繼承,理想的情況下,DefaultListableBeanFactory 需要繼承不同的trait,即單例註冊、FactoryBean註冊等功能模組,Configurable, Listable, **Capable恰好是這種設計思想的體現。如果最終的DefaultListableBeanFactory寫成一個類,一定是巨大的,但是假如我們將BeanFactory不同的特性做拆分的話,就會得到如圖所示的看似複雜的介面繼承關係。

這種鬆散的介面繼承關係正是我們需要的,舉例來說,autowireCapable 和 hierarchical並沒有實際上的聯絡,一個關注屬性注入,另一個則關注bean工廠的層級關係(可以有父工廠)。

假設每一個鬆散的介面都有幾個或多個實現,不管其是否是抽象類或者具體實現,我們只有通過多繼承或者委託模式組裝得到DefaultListableBeanFactory

這就是矛盾的地方,單一的繼承鏈和鬆散的介面,其結果就是抽象類具有了一些不必要的功能。比如AbstractAutowireCapableBeanFactory 具有了Configurable、aliasRegistry等能力。這種情況是我們閱讀原始碼是需要注意的。

Spring通過將單繼承鏈分解為6個類,將DefaultListableBeanFactory 進行了功能拆分,符合開閉原則,每個類也符合單一職責原則的要求。

通過以上分析,開啟DefaultListableBeanFactory 的原始碼,雖然有2000多行,我們可以清楚地看出類的結構,包括1. 繼承鏈相關:不同抽象方法的實現、未在抽象類中實現的介面方法的實現。2. BeanDefinitionRegistry 3. ConfigurableListableBeanFactory 4. Serializable

舉例2:

類似如上的分析,AnnotationConfigApplicationContext 的繼承鏈如下:

DefaultResourceLoader → AbstractApplicationContext → GenericApplicationContext → AnnotationConfigApplicationContext

每個類實現的功能即其直接實現的介面,有些類通過類名也可以快速得知其實現的功能。不再贅述。

GenericApplicationContext 實現了BeanDefinitionRegistry,直接委託BeanFactory的實現給DefaultListableBeanFactory,子類包括AnnotationConfigApplicationContext 和mvc容器等。

在抽象類AbstractApplicationContext可以看到大家耳熟能詳的refresh方法。

ApplicationContext更是重量級,作為應用容器,擁有BeanFactory外的事件廣播、國際化、資源讀取等能力。

由於ApplicatoinContext是大介面,是不同功能的最終整合,所以我們看到的介面繼承關係並不複雜。

舉例3:

我們知道MVC模型中具有中央排程器,在SpringMvc中體現為DispatcherServlet,其繼承鏈如圖。

瞭解過servlet的人都知道HttpServlet具有doGet、doPost等方法,子類重新後所有方法都轉發到DispatcherServlet中,doService→doDispatch執行了我們熟知的分發模板邏輯:簡單來說就是

getHandlergetHandlerAdapterapplyPreHandle**handle**applyPostHandleprocessDispatchResult

processDispatchResult中包含例外處理,render和afterCompletion

我們隨便選擇一個方法,比如初始化mvc容器,其必在繼承鏈上的某個類中進行實現,通過分析原始碼可以看出:

initWebApplicationContext在FrameworkServlet中實現。

GenericServlet暴露init方法。

HttpServletBean實現了init方法,在init方法中暴露initServletBean方法。

FrameworkServlet實現initServletBean方法,其中實現了initWebApplicationContext方法。

雖然執行初始化mvc容器方法需要在繼承鏈上來回跳轉,但是其實現了單一職責原則,每一個類負責實現了特定的功能,模板類實現了模板流程,實現類實現具體實現。