Spring原理初探----IOC、AOP

前言

說是Java web,Spring已經成為了事實標準,Spring原理的深入學習,無論是在工作中,還是在面試中,都尤為重要。

Spring的兩個核心概念是IOC(控制反轉)和AOP(面向切面編程)。想了解Spring的工作原理,毫無疑問,首先要從這兩個概念的Spring實現入手。但是Spring源碼浩如煙海,里面摻雜了太多的實現細節,入門可謂極其困難。當我正苦于難以入門時,好友介紹了tiny-spring這個開源項目,這個項目用了不到千行的代碼,就將Spring的IOC、AOP的核心流程實現完畢,真是居家旅行、吹逼面試之必備呀!

廢話少說,我們開始吧!

目錄結構

在github上clone下項目來之后,我們關注src文件夾,其余的是一些愛好者提的注釋PR,恰巧被作者merge了,不必理會。目錄結構是這樣的:


目錄結構
  1. aop包,顧名思義,實現了Spring的AOP功能,可以通過bean的自動AOP切入,文件稍多,暫時先不展開。
  2. bean.factory包,通過BeanFactoryAbstractBeanFactoryAutowireCapableBeanFactory三個類,實現了BeanFactory的核心功能,詳情稍后講解。
  3. bean.io包定義了資源加載相關的抽象概念,這里的資源包括xml配置文件等。
  4. bean.xml包中只包含一個類:XmlBeanDefinitionReader,主要負責在xml配置文件中讀取bean定義。
  5. bean包其他類,定義了BeanDefinition等核心概念,詳情后講。
  6. context包定義了ApplicationContext的核心概念。
  7. BeanReference指的是引用類型的Bean,而不是實體類。

IOC--浮沙筑臺之根基

IOC(控制翻轉)是一種編程范式,可以在一定程度上解決復雜系統對象耦合度太高的問題,并不是Spring的專利。IOC最常見的方式是DI(依賴注入),可以通過一個容器,將Bean維護起來,方便在其他地方直接使用,而不是重新new。可以說,IOC是Spring最基本的概念,沒有IOC就沒有Spring。

為什么DI可以起到解耦的作用?
一個軟件系統包含了大量的對象,每個對象之間都有依賴關系,在普通的軟件編寫過程中,對象的定義分布在各個文件之中,對象之間的依賴,只能通過類的構造器傳參,方法傳參的形式來完成。當工程變大之后,復雜的邏輯會讓對象之間的依賴梳理變得異常困難。
在Spring IOC中,一般情況,我們可以在XML文件之中,統一的編寫bean的定義,bean與bean之間的依賴關系,從而增加邏輯的清晰度。而且,bean的創建是由Spring來完成的,不需要編程人員關心,編程人員只需要將精力放到業務的邏輯上面,減輕了思維的負擔。

tiny-spring里面,整個beanscontext包都是用來實現IOC的。

beans包實現的核心關注點是BeanFactoryBeanFactory也叫作Bean容器,顧名思義,是用來盛放、管理bean的。
context包實現的核心關注是ApplicationContextApplicationContext也是用來獲取Bean的,但是它更高層,它的面向用戶是Spring的使用者,而BeanFactory面向的用戶更多是Spring開發者。BeanFactory定義了Bean初始化的流程,ApplicationContext定義了從XML讀取,到Bean初始化,再到使用的過程。

Bean在哪定義?

剛才有說到,Spring通常通過xml文件,來統一的描述bean,bean與bean的依賴關系。所以說,bean的定義表述,發生在xml配置文件之中。這個XML文件就是我們需要讀取的資源文件。

因此,首要任務就是研究與讀取XML資源文件相關的類。

bean.io中存放的是讀取資源文件的抽象概念。其中包含了三個類或者接口:

  1. Resource接口,這個接口只有一個方法,InputStream getInputStream() throws IOException;。實現這個接口的類就是一個抽象的資源,可以獲取這個資源的輸入流,從而獲取其中的內容。
  2. UrlResource類,這個類實現了Resource接口,通過構造器傳入一個url地址,代表的是這個url所對應的文件。
  3. ResourceLoader類,只有一個方法,public Resource getResource(String location)。輸入url的文件地址(并不是真正的URL格式地址),來獲取Resource。

通過分析上面三個類、接口,我們知道,這個包完成了一個任務:通過ResourceLoader這個類,獲取某一個地址的Resource,從而獲取這個文件的輸入流。因為使用了Resource概念,可以使用網絡文件或者本地文件。

Bean如何定義?

  1. BeanDefinition是Bean定義的核心概念,BeanDefinition包含了:bean的對象、bean的類類型、bean的名字,bean的所有屬性。這個類對bean的基本信息做好一個包裝。
  2. BeanDefinitionReader接口,只有一個方法:void loadBeanDefinitions(String location) throws Exception;,實現這個接口的類,具有將某一個文件中的所有bean定義載入的功能。所以BeanDefinitionReader定義了,在哪載入bean定義,至于載入到哪里、如何載入,稍后看具體實現。
  3. AbstractBeanDefinitionReader抽象類,上面剛說了實現了BeanDefinitionReader接口的類,具有將某一個文件中描述的bean定義載入的功能,AbstractBeanDefinitionReader就實現了這樣一個抽象功能。它的作用就是定義,載入到哪和如何載入的問題。在這個類里面,有兩個屬性:Map<String,BeanDefinition> registry;ResourceLoader resourceLoader;registry是一個注冊表,他保存的就是所有的Bean定義,Map結構,key是bean的名字,value就是BeanDefinition。resourceLoader描述了如何載入。
  4. XmlBeanDefinitionReader這是beans.xml包里面的唯一一個方法,也是最重要的方法之一。它繼承了AbstractBeanDefinitionReader,實現了所有方法,解決了bean定義中:在哪載入、如何載入、載入到哪的三個大問題。這個類面向用戶的方法有兩個,一個是loadBeanDefinitions,毫無疑問,這個是必須的。另一個是getRegistry用來獲取bean注冊表,得到所有bean的信息,registry是bean們在內存中實際的家。但是這個getRegistry方法并不是面向用戶的,而是面向ApplicationContext的。
  5. PropertyValuePropertyValue代表一種抽象概念,在xml中,bean的屬性包括屬性名和屬性對象,PropertyValue就是這么一個實體。
  6. BeanReference代表的是Bean的屬性不是真實對象,而是另一個bean的引用。

Bean的組裝全過程

上面兩部分是鋪墊,而BeanFactory才是重點對象。beans.factory包中有三個類用來定義BeanFactory相關的概念。

  1. BeanFactory接口,只有一個方法:Object getBean(String name) throws Exception;,實現這個接口的類,就具有了得到一個bean的能力。
  2. AbstractBeanFactory類,較為復雜。詳情后講。
  3. AutowireCapableBeanFactory繼承了AbstractBeanFactory,實現了applyPropertyValues方法,通過反射,將bean的所有屬性,通過set方法注入進去。

AbstractBeanFactory有三大屬性:

  • beanDefinitionMap,類似于registry,但是他是BeanFactory里面私有的,代表的是這個BeanFactory里面暫時有哪些bean定義。
  • beanDefinitionNames代表里面,這個BeanFactory里面有哪些bean(名字)。
  • beanPostProcessors,代理處理器,AOP會用到,詳情后講。

AbstractBeanFactory實現了幾大功能:

  • getBean,這是主要功能,可以獲取一個Bean對象。
  • registerBeanDefinition,面向ApplicationContext,用來將XML配置文件里面的bean定義注冊到BeanFactory里面。
  • preInstantiateSingletons,面向ApplicationContext,用來在開始的時候,將所有的bean都初始化完畢,避免懶加載。
  • addBeanPostProcessor添加代理處理器。
  • getBeansForType,在BeanFactory里面,獲取某一個類型的所有bean。

經過上面的分析,我們可以知道BeanFactory完成了Bean初始化的整個流程。BeanFactory的工作流程如下:

  1. getBean, 在beanDefinitionMap里面得到bean,如果沒有的話,先初始化。(為什么會沒有,因為ApplicationContext讀取xml文件時候,只是給BeanDefinition服了類類型,并沒有賦值對象,這個對象還是需要BeanFactory通過反射生成的)。
  2. createBeanInstance,通過反射,根據BeanDefinition的類對象新建實體對象->將得到的bean對象賦值給beandefinition,然后將BeanDefinition里面的屬性都注入到Bean里面,這就完成了doCreateBean。
  3. initializeBean就是調用BeanPostProcessor的postProcessBeforeInitilizztion方法和postProcessAfterIntilizatin方法,獲取新的bean,這里會在aop中用到。

好了,到這BeanFactory就講完了,下面是更重要的ApplicationContext。

ApplicationContext-用戶與BeanFactory之間的橋梁

beans.context包有三個類、接口,完成了ApplicationContext的基本功能。

  1. ApplicationContext接口,沒有任何方法,只是繼承了BeanFactory接口,暗示ApplicationContext與BeanFactory都是獲取Bean的地方。
  2. AbstractApplicationContext抽象類,首先,它的構造函數接收入參BeanFactory,所以說ApplicationContext內部具有一個BeanFactory。類似于一種裝飾器模式,但不是裝飾器模式,類似于代理模式,但也不是代理模式。fresh方法分為三個步驟:1.loadBeanDefinitions,這個是一個模板方法,需要子類實現,它的作用就是從某一個地方讀取BeanDefinition,然后寫入到ApplicationContext自己的BeanFactory里面,這就是ApplicationContext與BeanFactory之間的聯系,也就是ApplicationContext還負責了讀取定義。2. registerBeanPostProcessors,這個就是在BeanFactory里面找到BeanPostProcessor,然后將他們放到BeanFactory的beanPostProcessors容器里面,方便BeanFactory初始化使用。3. onRefresh初始化一遍所有的bean。
  3. ClassPathXmlApplicationContext實現了loadBeanDefinitions的方法,將xml文件和BeanFactory結合在一起。

總結-ApplicationContext初始化流程

ApplicationContext初始化流程

總結-ApplicationContext獲取bean流程

ApplicationContext getBean

AOP--移花接木之魔法

上一節,講完了Spring IOC的整個流程,也就是bean從定義獲取,到得到bean之間的整個流程。本節,我們接觸一下Spring另一個重要概念,AOP。AOP用途十分廣泛,其中Spring內部的聲明式事務和攔截器都是利用了AOP的強大威力,才得以優雅的實現。
AOP是什么呢,簡單來說,它可以讓編程人員在不修改對象代碼的情況下,為這個對象添加額外的功能或者限制。
很熟悉吧,這就是代理模式!

Java中存在兩種代理模式:
一種叫靜態代理,就是通過接口繼承復用的方式來完成的, 代理類與被代理對象實現相同的接口,然后代理類里面會擁有一個被代理對象,代理類與被代理對象相同的方法,活調用被代理對象的方法,不過中間會加以限制,您翻開任何一本設計模式相關的書,翻到代理模式這一節,講的就是它了。

另一種叫做動態代理,動態代理就是允許我們在程序運行過程中,為動態生成的對象,動態的生成代理。顯然,這比靜態代理靈活太多了。
Java默認提供了動態代理的實現方式,但是有限制,它要求被代理對象必須實現某一個接口。為了突破這一限制,為普通類也可以提供代理,CGLib這個庫橫空出世。

因為AOP涉及的知識較為復雜,所以我先將背景知識介紹一下。

  1. Java動態代理,就是Java本身提供的代理實現,要求對象必須實現某一個接口。
  2. CGLib庫,為Java提供了,為普通類提供代理的功能。
  3. aopalliance,aop聯盟包,統一類aop編程的一些概念,這個包里沒有具體的類實現,而是定義了幾個重要的概念接口,具體的aop實現,要遵從這些接口編程,才能達到一定的通用性。
  4. aspectj包,實現了,通過一種固定的編程語言,通過這種簡單的編程語言,我們可以定位到被代理的類,自動完成代理。

在aopallicance里面,定義了幾個核心概念:

  1. Advice增強,說明這是一個,實現這個接口,說明這個類負責某一種形式的增強。
  2. Joinpoint連接點,表示切點與目標方法連接處的信息。
  3. MethodInterceptor繼承了Interceptor接口,而Interceptor繼承了Advice接口,是一種Advice,但是有一個方法invoke。這個方法需要一個參數MethodInvocation。
  4. MethodInvocation表示的是連接點的信息以及連接點函數的調用。
    結合上面的信息,我們發現,其實MethodInterceptor的invoke方法,調用的就是MethodInvocation的proceed方法,而這個proceed方法呢,應該調用的肯定是Method.invoke方法。所以,這是一種變相調用method.invoke的方式。為什么這樣做呢,猜一猜的話,肯定是為了代碼的復用,哈哈哈,這是廢話。
    在Spring中,還定義了幾個核心概念:
  5. Pointcut,切點,可以定位類以及方法
  6. Advisor,可以獲取一個增強。
  7. PointcutAdvisor,定義了哪些方法,具有什么類型的增強。
  8. MethodMatcher表示某一個方法是否符合條件
  9. ClassFilter 定義了某個類是否符合條件。
  10. TargetSource被代理的對象,包括對象本身,類類型、所有接口
  11. AdvisedSupport代理相關的元數據,包括被代理的對象,增強等。
  12. ProxyFactory,代理工廠,可以獲得一個代理對象,同時具有AdvisedSupport的所有特性。
  13. Cglib2AopProxy,使用cglib實現的動態代理類,繼承了AbstractAopProxy抽象類,這個類的主要方法就是getProxy,通過什么呢,通過AdvisorSupport。
  14. ReflectiveMethodInvocation可以獲取連接點的信息,代理的對象等。
  15. JdkDynamicAopProxy,和Cglib2AopProxy類一個作用,通過AdvisorSupport來getProxy,不過是使用Java自帶的動態代理實現的。
    其中,ProxyFactory是獲取一個代理對象的直接工廠,而這個代理對象,可以通過Cglib2AopProxy產生,也可以通過JdkDynamicAopProxy產生。

Spring AOP之所以能夠為動態生成的Bean提供代理,得益于PostProcessor接口。我們會議IOC初始化流程中,最后一部,就是得到BeanFactory之中所有繼承了PostProcessor接口的bean,調用它們的postProcessBeforeInitilization、postProcessAfterInitilization方法,來代理bean,生成新的bean。

基于這個突破口,我們只需要在xml配置文件中,放入PostProcessor對象,Spring就會自動的用這寫對象,來代理真正的對象。


aop目錄

在這里,我們的對象是AspectJAwareAdvisorAutoProxyCreator。
在這個對象的方法中,邏輯是這樣的,找到xml里面所有切面bean,然后在這些bean里面,找到符合被代理類的切面bean,找到切面bean之后,就可以獲得增強,切點等,于是可有構造一個AdvisorSupport,知道了AdvisorSupport,我們就能夠通過proxyFactory來獲取代理了。

至于如何這個類切面是用來切入代理類的,這個就要交給PointCut來實現了,pointcut有很多實現方式,這里我們用的是aspectj。具體這個類我就不細講了。

到目前位置,我自己已經將整個AOP的流程搞清楚了,下面通過流程圖的形式展示出來:


AOP流程
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容