轉自:www.zybuluo.com/dugu9sword/…
程式碼地址 :github.com/lingo0/tiny…
這個是我fork的程式碼,我在其中加入了一些分析,同時我準備在這個基礎上實現利用註解來注入。
下面是原文。
前言
在閱讀 Spring 的原始碼(依賴注入部分和麵向切面程式設計部分)時遇到不少困惑,龐大的類檔案結構、紛繁複雜的方法呼叫、波詭雲譎的多型實現,讓自己深陷其中、一頭霧水。 後來注意到 code4craft 的 tiny-spring 專案,實現了一個微型的 Spring,提供對 IoC 和 AOP 的最基本支援,麻雀雖小,五臟俱全,對 Spring 的認知清晰了不少。這個微型框架的結構包括檔名、方法名都是參照 Spring 來實現的,對於初讀 Spring 的學習者,作為研究 Spring 的輔助工具應該能夠受益匪淺。 在研究 tiny-spring 的時候,收穫頗多,把對這個微型框架的一些分析寫了下來,行文可能有點紊亂。
本文結構
- 第一部分 IoC 容器的實現 對應了 tiny-spring 的 step-1 到 step-5 部分,這 5 個 step 實現了基本的 IoC 容器,支援singleton型別的bean,包括初始化、屬性注入、以及依賴 Bean 注入,可從 XML 中讀取配置,XML 讀取方式沒有具體深入。
- 第二部分 AOP 容器的實現 對應了 tiny-spring 的 step-6 到 step-9 部分。step-10 中對 cglib 的支援沒有分析。這 4 個 step 可以使用 AspectJ 的語法進行 AOP 編寫,支援介面代理。考慮到 AspectJ 語法僅用於實現
execution("***")
部分的解析,不是主要內容,也可以使用 Java 的正規表示式粗略地完成,因此沒有關注這些細節。參考書目 《Spring 實戰》《Spring 技術內幕》
IoC 容器的實現
檔案結構
Resource
以 Resource
介面為核心發散出的幾個類,都是用於解決 IoC 容器中的內容從哪裡來的問題,也就是 配置檔案從哪裡讀取、配置檔案如何讀取 的問題。
類名 | 說明 |
---|---|
Resource |
介面,標識一個外部資源。通過 getInputStream() 方法 獲取資源的輸入流 。 |
UrlResource |
實現 Resource 介面的資源類,通過 URL 獲取資源。 |
ResourceLoader |
資源載入類。通過 getResource(String) 方法獲取一個 Resouce 物件,是 獲取 Resouce 的主要途徑 。 |
注: 這裡在設計上有一定的問題,ResourceLoader
直接返回了一個 UrlResource
,更好的方法是宣告一個 ResourceLoader
介面,再實現一個 UrlResourceLoader
類用於載入 UrlResource
。
BeanDefinition
以 BeanDefinition
類為核心發散出的幾個類,都是用於解決 Bean
的具體定義問題,包括 Bean
的名字是什麼、它的型別是什麼,它的屬性賦予了哪些值或者引用,也就是 如何在 IoC 容器中定義一個 Bean,使得 IoC 容器可以根據這個定義來生成例項 的問題。
類名 | 說明 |
---|---|
BeanDefinition |
該類儲存了 Bean 定義。包括 Bean 的 名字 String beanClassName 、型別Class beanClass 、屬性 PropertyValues propertyValues 。根據其 型別 可以生成一個類例項,然後可以把 屬性 注入進去。propertyValues 裡面包含了一個個 PropertyValue 條目,每個條目都是鍵值對 String - Object ,分別對應要生成例項的屬性的名字與型別。在 Spring 的 XML 中的 property 中,鍵是 key ,值是 value 或者 ref 。對於 value 只要直接注入屬性就行了,但是 ref 要先進行解析。Object 如果是 BeanReference 型別,則說明其是一個引用,其中儲存了引用的名字,需要用先進行解析,轉化為對應的實際 Object 。 |
BeanDefinitionReader |
解析 BeanDefinition 的介面。通過 loadBeanDefinitions(String) 來從一個地址載入類定義。 |
AbstractBeanDefinitionReader |
實現 BeanDefinitionReader 介面的抽象類(未具體實現 loadBeanDefinitions ,而是規範了 BeanDefinitionReader 的基本結構)。內建一個 HashMap rigistry ,用於儲存 String - beanDefinition 的鍵值對。內建一個 ResourceLoader resourceLoader ,用於儲存類載入器。用意在於,使用時,只需要向其 loadBeanDefinitions() 傳入一個資源地址,就可以自動呼叫其類載入器,並把解析到的 BeanDefinition 儲存到 registry 中去。 |
XmlBeanDefinitionReader |
具體實現了 loadBeanDefinitions() 方法,從 XML 檔案中讀取類定義。 |
BeanFactory
以 BeanFactory
介面為核心發散出的幾個類,都是用於解決 IoC 容器在 已經獲取 Bean 的定義的情況下,如何裝配、獲取 Bean 例項 的問題。
類名 | 說明 |
---|---|
BeanFactory |
介面,標識一個 IoC 容器。通過 getBean(String) 方法來 獲取一個物件 |
AbstractBeanFactory |
BeanFactory 的一種抽象類實現,規範了 IoC 容器的基本結構,但是把生成 Bean 的具體實現方式留給子類實現。IoC 容器的結構:AbstractBeanFactory 維護一個 beanDefinitionMap 雜湊表用於儲存類的定義資訊(BeanDefinition )。獲取 Bean 時,如果 Bean 已經存在於容器中,則返回之,否則則呼叫 doCreateBean 方法裝配一個 Bean 。(所謂存在於容器中,是指容器可以通過 beanDefinitionMap 獲取 BeanDefinition 進而通過其 getBean() 方法獲取 Bean 。) |
AutowireCapableBeanFactory |
可以實現自動裝配的 BeanFactory 。在這個工廠中,實現了 doCreateBean 方法,該方法分三步:1,通過 BeanDefinition 中儲存的類資訊例項化一個物件;2,把物件儲存在 BeanDefinition 中,以備下次獲取;3,為其裝配屬性。裝配屬性時,通過 BeanDefinition 中維護的 PropertyValues 集合類,把 String - Value 鍵值對注入到 Bean 的屬性中去。如果 Value 的型別是 BeanReference 則說明其是一個引用(對應於 XML 中的 ref ),通過 getBean 對其進行獲取,然後注入到屬性中。 |
ApplicationContext
以 ApplicationContext
介面為核心發散出的幾個類,主要是對前面 Resouce
、 BeanFactory
、BeanDefinition
進行了功能的封裝,解決 根據地址獲取 IoC 容器並使用 的問題。
類名 | 說明 |
---|---|
ApplicationContext |
標記介面,繼承了 BeanFactory 。通常,要實現一個 IoC 容器時,需要先通過 ResourceLoader 獲取一個 Resource ,其中包括了容器的配置、Bean 的定義資訊。接著,使用 BeanDefinitionReader 讀取該 Resource 中的 BeanDefinition 資訊。最後,把 BeanDefinition 儲存在 BeanFactory 中,容器配置完畢可以使用。注意到 BeanFactory 只實現了 Bean 的 裝配、獲取,並未說明 Bean 的 來源 也就是 BeanDefinition 是如何 載入 的。該介面把 BeanFactory 和 BeanDefinitionReader 結合在了一起。 |
AbstractApplicationContext |
ApplicationContext 的抽象實現,內部包含一個 BeanFactory 類。主要方法有 getBean() 和 refresh() 方法。getBean() 直接呼叫了內建 BeanFactory 的 getBean() 方法,refresh() 則用於實現 BeanFactory 的重新整理,也就是告訴 BeanFactory 該使用哪個資源(Resource )載入類定義(BeanDefinition )資訊,該方法留給子類實現,用以實現 從不同來源的不同型別的資源載入類定義 的效果。 |
ClassPathXmlApplicationContext | 從類路徑載入資源的具體實現類。內部通過 XmlBeanDefinitionReader 解析 UrlResourceLoader 讀取到的 Resource ,獲取 BeanDefinition 資訊,然後將其儲存到內建的 BeanFactory 中。 |
*注 1:*在 Spring 的實現中,對 ApplicatinoContext
的分層更為細緻。AbstractApplicationContext
中為了實現 不同來源 的 不同型別 的資源載入類定義,把這兩步分層實現。以“從類路徑讀取 XML 定義”為例,首先使用 AbstractXmlApplicationContext
來實現 不同型別 的資源解析,接著,通過 ClassPathXmlApplicationContext
來實現 不同來源 的資源解析。
*注 2:*在 tiny-spring 的實現中,先用 BeanDefinitionReader
讀取 BeanDefiniton
後,儲存在內建的 registry
(鍵值對為 String
- BeanDefinition
的雜湊表,通過 getRigistry()
獲取)中,然後由 ApplicationContext
把 BeanDefinitionReader
中 registry
的鍵值對一個個賦值給 BeanFactory
中儲存的 beanDefinitionMap
。而在 Spring 的實現中,BeanDefinitionReader
直接操作 BeanDefinition
,它的 getRegistry()
獲取的不是內建的 registry
,而是 BeanFactory
的例項。如何實現呢?以 DefaultListableBeanFactory
為例,它實現了一個 BeanDefinitonRigistry
介面,該介面把 BeanDefinition
的 註冊 、獲取 等方法都暴露了出來,這樣,BeanDefinitionReader
可以直接通過這些方法把 BeanDefiniton
直接載入到 BeanFactory
中去。
設計模式
注:此處的設計模式分析不限於 tiny-spring,也包括 Spring 本身的內容
模板方法模式
該模式大量使用,例如在 BeanFactory
中,把 getBean()
交給子類實現,不同的子類 **BeanFactory
對其可以採取不同的實現。
代理模式
在 tiny-spring 中(Spring 中也有類似但不完全相同的實現方式),ApplicationContext
繼承了 BeanFactory
介面,具備了 getBean()
功能,但是又內建了一個 BeanFactory
例項,getBean()
直接呼叫 BeanFactory
的 getBean()
。但是ApplicationContext
加強了 BeanFactory
,它把類定義的載入也包含進去了。
AOP 的實現
重新分析 IoC 容器
注:以下所說的 BeanFactory 和 ApplicationContext 不是指的那幾個最基本的介面類(例如 BeanFactory 介面,它除了 getBean 空方法之外,什麼都沒有,無法用來分析。),而是指這一類物件總體的表現,比如 ClasspathXmlApplicationContext、FileSystemXmlApplicationContext 都算是 ApplicationContext。
BeanFactory 的構造與執行
BeanFactory
的核心方法是 getBean(String)
方法,用於從工廠中取出所需要的 Bean
。AbstractBeanFactory
規定了基本的構造和執行流程。
getBean
的流程:包括例項化和初始化,也就是生成 Bean,再執行一些初始化操作。
doCreateBean
:例項化Bean
。 a.createInstance
:生成一個新的例項。 b.applyProperties
:注入屬性,包括依賴注入的過程。在依賴注入的過程中,如果Bean
實現了BeanFactoryAware
介面,則將容器的引用傳入到Bean
中去,這樣,Bean
將獲取對容器操作的許可權,也就允許了 編寫擴充套件 IoC 容器的功能的 Bean。initializeBean(bean)
: 初始化Bean
。 a. 從BeanPostProcessor
列表中,依次取出BeanPostProcessor
執行bean = postProcessBeforeInitialization(bean,beanName)
。(為什麼呼叫 BeanPostProceesor 中提供方法時,不是直接 post...(bean,beanName) 而是 bean = post...(bean,beanName) 呢?見分析1 。另外,BeanPostProcessor 列表的獲取有問題,見分析2。) b. 初始化方法(tiny-spring 未實現對初始化方法的支援)。 c. 從BeanPostProcessor
列表中, 依次取出BeanPostProcessor
執行其bean = postProcessAfterInitialization(bean,beanName)
。
ApplicationContext 的構造和執行
ApplicationContext
的核心方法是 refresh()
方法,用於從資原始檔載入類定義、擴充套件容器的功能。
refresh
的流程:
loadBeanDefinitions(BeanFactory)
:載入類定義,並注入到內建的BeanFactory
中,這裡的可擴充套件性在於,未對載入方法進行要求,也就是可以從不同來源的不同型別的資源進行載入。registerBeanPostProcessors(BeanFactory)
:獲取所有的BeanPostProcessor
,並註冊到BeanFactory
維護的BeanPostProcessor
列表去。onRefresh
: a.preInstantiateSingletons
:以單例的方式,初始化所有Bean
。tiny-spring 只支援singleton
模式。
IoC 實現的一些思考與分析
分析 1:AOP 可以在何處被嵌入到 IoC 容器中去?
在 Bean
的初始化過程中,會呼叫 BeanPostProcessor
對其進行一些處理。在它的 postProcess...Initialization
方法中返回了一個 Bean
,這個返回的 Bean
可能已經不是原來傳入的 Bean
了,這為實現 AOP 的代理提供了可能!以 JDK 提供的動態代理為例,假設方法要求傳入的物件實現了 IObj
介面,實際傳入的物件是 Obj
,那麼在方法中,通過動態代理,可以 生成一個實現了 IObj 介面並把 Obj 作為內建物件的代理類 Proxy 返回,此時 Bean
已經被偷偷換成了它的代理類。
分析 2:BeanFactory 和 ApplicationContext 設計上的耦合
BeanFactory
中的 BeanPostProcessor
的列表是哪裡生成的呢?是在 ApplicationContext
中的 refresh
方法的第二步,這裡設計上應該有些問題,按理說 ApplicationContext
是基於 BeanFactory
的,BeanFactory
的屬性的獲取,怎麼能依賴於 ApplicationContext
的呼叫呢?
分析 3:tiny-spring 總體流程的分析
總體來說,tiny-spring 的 ApplicaitonContext
使用流程是這樣的:
ApplicationContext
完成了類定義的讀取和載入,並註冊到BeanFactory
中去。ApplicationContext
從BeanFactory
中尋找BeanPostProcessor
,註冊到BeanFactory
維護的BeanPostProcessor
列表中去。ApplicationContext
以單例的模式,通過主動呼叫getBean
例項化、注入屬性、然後初始化BeanFactory
中所有的Bean
。由於所有的BeanPostProcessor
都已經在第 2 步中完成例項化了,因此接下來例項化的是普通Bean
,因此普通Bean
的初始化過程可以正常執行。 \4. 呼叫getBean
時,委託給BeanFactory
,此時只是簡單的返回每個Bean
單例,因為所有的Bean
例項在第三步都已經生成了。
JDK 對動態代理的支援
JDK 中幾個關鍵的類:
類名 | 說明 |
---|---|
Proxy |
來自 JDK API。提供生成物件的動態代理的功能,通過Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法返回一個代理物件。 |
InvocationHandler |
來自 JDK API。通過 Object invoke(Object proxy, Method method,Object[] args) 方法實現代理物件中方法的呼叫和其他處理。 |
假設以下的情況:
- 物件
obj
實現了IObj
介面,介面中有一個方法func(Object[] args)
。 - 物件
handler
是InvocationHandler
的例項。
那麼,通過 Proxy
的 newProxyInstance(obj.getClassLoader(), obj.getClass().getInterfaces(), handler
,可以返回 obj
的代理物件 proxy
。
當呼叫 proxy.func(args)
時,物件內部將委託給 handler.invoke(proxy, func, args)
函式實現。
因此,在 handler 的 invoke 中,可以完成對方法攔截的處理。可以先判斷是不是要攔截的方法,如果是,進行攔截(比如先做一些操作,再呼叫原來的方法,對應了 Spring 中的前置通知);如果不是,則直接呼叫原來的方法。
AOP 的植入與實現細節
在 Bean
初始化過程中完成 AOP 的植入
解決 AOP 的植入問題,首先要解決 在 IoC 容器的何處植入 AOP 的問題,其次要解決 為哪些物件提供 AOP 的植入 的問題。
tiny-spring 中 AspectJAwareAdvisorAutoProxyCreator
類(以下簡稱 AutoProxyCreator
)是實現 AOP 植入的關鍵類,它實現了兩個介面:
BeanPostProcessor
:在postProcessorAfterInitialization
方法中,使用動態代理的方式,返回一個物件的代理物件。解決了 在 IoC 容器的何處植入 AOP 的問題。BeanFactoryAware
:這個介面提供了對BeanFactory
的感知,這樣,儘管它是容器中的一個Bean
,卻可以獲取容器的引用,進而獲取容器中所有的切點物件,決定對哪些物件的哪些方法進行代理。解決了 為哪些物件提供 AOP 的植入 的問題。
AOP 中動態代理的實現步驟
動態代理的內容
首先,要知道動態代理的內容(攔截哪個物件、在哪個方法攔截、攔截具體內容),下面是幾個關鍵的類:
類名 | 說明 |
---|---|
PointcutAdvisor |
切點通知器,用於提供 對哪個物件的哪個方法進行什麼樣的攔截 的具體內容。通過它可以獲取一個切點物件 Pointcut 和一個通知器物件 Advisor 。 |
Pointcut |
切點物件可以獲取一個 ClassFilter 物件和一個 MethodMatcher 物件。前者用於判斷是否對某個物件進行攔截(用於 篩選要代理的目標物件),後者用於判斷是否對某個方法進行攔截(用於 在代理物件中對不同的方法進行不同的操作)。 |
Advisor |
通知器物件可以獲取一個通知物件 Advice 。就是用於實現 具體的方法攔截,需要使用者編寫,也就對應了 Spring 中的前置通知、後置通知、環切通知等。 |
動態代理的步驟
接著要知道動態代理的步驟:
AutoProxyCreator
(實現了BeanPostProcessor
介面)在例項化所有的Bean
前,最先被例項化。- 其他普通
Bean
被例項化、初始化,在初始化的過程中,AutoProxyCreator
載入BeanFactory
中所有的PointcutAdvisor
(這也保證了PointcutAdvisor
的例項化順序優於普通Bean
。),然後依次使用PointcutAdvisor
內建的ClassFilter
,判斷當前物件是不是要攔截的類。 - 如果是,則生成一個
TargetSource
(要攔截的物件和其型別),並取出AutoProxyCreator
的MethodMatcher
(對哪些方法進行攔截)、Advice
(攔截的具體操作),再,交給AopProxy
去生成代理物件。 AopProxy
生成一個InvocationHandler
,在它的invoke
函式中,首先使用MethodMatcher
判斷是不是要攔截的方法,如果是則交給Advice
來執行(Advice
由使用者來編寫,其中也要手動/自動呼叫原始物件的方法),如果不是,則直接交給TargetSource
的原始物件來執行。
設計模式
代理模式
通過動態代理實現,見分析1中的內容,不再贅述。
策略模式
生成代理物件時,可以使用 JDK 的動態代理和 Cglib 的動態代理,對於不同的需求可以委託給不同的類實現。
為 tiny-spring 新增攔截器鏈
tiny-spring 不支援攔截器鏈,可以模仿 Spring 中攔截器鏈的實現,實現對多攔截器的支援。
tiny-spring 中的 proceed()
方法是呼叫原始物件的方法 method.invoke(object,args)
。(參見 ReflectiveMethodInvocation
類)
為了支援多攔截器,做出以下修改:
- 將
proceed()
方法修改為呼叫代理物件的方法method.invoke(proxy,args)
。 - 在代理物件的
InvocationHandler
的invoke
函式中,檢視攔截器列表,如果有攔截器,則呼叫第一個攔截器並返回,否則呼叫原始物件的方法。