從Spring中學到的【1】–讀懂繼承鏈
最近看了一些 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
)
同時我們還可以看到,基本上每一個抽象類都對應一個實現的接口:
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方法。
HttpServletBean實現了init方法,在init方法中暴露
initServletBean
方法。FrameworkServlet實現
initServletBean
方法,其中實現了initWebApplicationContext
方法。
雖然執行初始化mvc容器方法需要在繼承鏈上來回跳轉,但是其實現了單一職責原則,每一個類負責實現了特定的功能,模板類實現了模板流程,實現類實現具體實現。