最近看了一些 Spring 源碼,發現源碼分析的文章很多,而底層思想分析的文章比較少,這個系列文章準備總結一下Spring中給我的啟示,包括設計模式思想、SOLID設計原則等,涉及一些程式設計的基本原則,雖然看似簡單,實則“小道理、大學問”。
我盡量遇到的問題談起,再說解決方案,同時至少舉兩個例子。
這些方法都是基于我遇到的一些實際代碼,掌握了基本思想,就可以舉一反三。
讓人頭暈眼花的跳轉
如果你通過某些教育訓練機構的源碼課,就會發現他們的老師在講源碼的時候在類之間、方法之間不停地跳,學員一臉懵逼。因為如果不了解老師講課的思路,或者是稍微走一下神,就會覺得自己跟不上了。
其實,問題就在于需要了解源碼的基本流程和繼承鍊的這種單一職責原則。
讀懂繼承鍊
面向對象的特點是封裝繼承和多态,封裝展現在私有方法。
在Spring中,常見的模式是頂層為接口,然後是抽象類,最後是各個實作類。
接口負責對外暴露,符合面向接口程式設計的準則,在更改實作類時,可以減小對于代碼的改動,保證了代碼依賴于抽象(接口)。.
抽象類一般使用模闆模式,實作了邏輯的組裝,把子類中的公用邏輯抽取出來,友善子類編寫;模闆方法一般為固定的執行鍊,我們讀源碼時,可以予以關注。比如常見的AbstractApplicationContext::refresh 方法,AbstractBeanFactory::doGetBean方法。由于接口一般隻能暴露方法聲明,抽象類可以實作一些狀态的getter,settter,這樣子類通路這些狀态資料隻需要調用方法即可。
實作類通常有多個,比如todo。有些時候當實作類隻有一個或者有一個預設實作類時,常常使用default命名,比如
DefaultListableBeanFactory.
讀源碼時可以選擇地閱讀預設實作。
Java 隻支援單繼承,這種語言層面的規範友善了我們閱讀源碼,一個方法的實作必然在一條繼承鍊中。
舉例1:
Spring中BeanFactory的預設實作是
DefaultListableBeanFactory
, 通過繼承圖可以看出,有多個抽象類,從上到下分别實作了以下的能力:
SimpleAliasRegistry
DefaultSingletonBeanRegistry
FactoryBeanRegistrySupport
AbstractBeanFactory
AbstractAutowireCapableBeanFactory
同時我們還可以看到,基本上每一個抽象類都對應一個實作的接口:
AliasRegistry
←
SimpleAliasRegistry
SingletonBeanRegistry
←
DefaultSingletonBeanRegistry
AbstractBeanFactory
←
ConfigurableBeanFactory
AbstractAutowireCapableBeanFactory
←
AutowireCapableBeanFactory
注意到 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執行了我們熟知的分發模闆邏輯:簡單來說就是
getHandler
→
getHandlerAdapter
→
applyPreHandle
→
**handle**
→
applyPostHandle
→
processDispatchResult
processDispatchResult中包含異常處理,render和afterCompletion
我們随便選擇一個方法,比如初始化mvc容器,其必在繼承鍊上的某個類中進行實作,通過分析源碼可以看出:
initWebApplicationContext
在FrameworkServlet中實作。
GenericServlet暴露init方法。