Spring_IOC_02——原理解析

如果對IOC的概念還不是很清楚,可以先閱讀上一篇文章:Spring_IOC_01——概念講解

Spring IOC 體系結(jié)構(gòu)

Spring提供了 IoC容器 來管理和容納我們所開發(fā)出的各種各樣的Bean,并且我們可以從中獲取各種發(fā)布在Spring IoC 容器里的Bean已添,并且通過描述可以得到它卿堂。

Spring IoC 容器設(shè)計

Spring IoC 容器主要基于以下兩個接口:

  • BeanFactory
  • ApplicationContext

其中赊堪,Spring IoC 容器所定義的最底層接口是BeanFactory。它有很多實現(xiàn)類震缭,ApplicationContext就是Spring IOC容器最高級接口之一,它是BeanFactory的其中一個子接口战虏,并且對BeanFactory功能做了很多擴展拣宰,所以絕大部分情況下我們都推薦使用 ApplicationContext 作為 Spring IoC 容器。

繼承關(guān)系

BeanFactory

從上圖我們可以看到烦感,BeanFactory位于設(shè)計的最底層巡社,它定義了IoC容器的基本規(guī)范。我們先看它提供了哪些方法:


BeanFactory

在BeanFactory里手趣,只對IOC容器的基本行為做了定義晌该,而不關(guān)系bean是如何定義怎樣加載的,正如我們只關(guān)系工廠里得到聲明的產(chǎn)品對像绿渣,而不關(guān)心工廠的具體生產(chǎn)操作朝群,從名字也可以看出,這是一個典型的工廠模式中符。
由于這個接口的重要性姜胖,有必要對其中關(guān)鍵接口定義做一下簡要說明:

  • getBean:
    對應(yīng)了多個方法來獲取配置給 Spring IoC 容器的Bean。
    1)按照類型獲取Bean:
    bean = (Bean) factory.getBean(Bean.class);
    注意:要求在Spring中只配置了一個這種類型的實例淀散,否則會報錯右莱。
    2)按照bean的ID獲取bean:
    bean = (Bean) factory.getBean("beanName");
    注意:這種方式并不安全,IDE不會檢查其安全性(關(guān)聯(lián)性)
    3)根據(jù)類型和ID獲取bean:(推薦
    bean = (Bean) factory.getBean("beanName", Bean.class);
    這種方式通過ID獲取Bean档插,獲取的同時也可以對Bean進行類型檢查慢蜓。

  • isSingleton 和 isPrototype:
    用于判斷該Bean的類型,是單例的還是原型的郭膛。如果是單例的胀瞪,則該Bean在容器中是作為唯一一個單例而存在的。如果是原型的,則每次從容器中獲取Bean都會生成一個新的實例凄诞。默認(rèn)情況下Spring容器中的Bean都是單例的圆雁。(具體bean的作用域接下來會講)

  • getAliases:
    用于獲取別名的方法。

ApplicationContext

根據(jù)ApplicationContext 的類繼承關(guān)系圖帆谍,可以看到 ApplicationContext 接口擴展了許多接口伪朽,因此它的功能十分強大,所以在實際應(yīng)用中常用到的事 ApplicationContext 接口汛蝙。

BeanFactory和ApplicationContext的區(qū)別

  • BeanFactory:
    是Spring中最底層的接口烈涮,只提供了最簡單的IoC功能,負(fù)責(zé)配置窖剑,創(chuàng)建和管理Bean坚洽。

  • ApplicationContext:
    實際應(yīng)用中一般推薦使用該接口,它繼承自BeanFactory西土,擁有最基本的IoC功能讶舰。
    除此之外,還提供了以下擴展功能
    1)支持信息源需了,可以實現(xiàn)國際化跳昼。(實現(xiàn)了 MessagsSource 接口)
    2)支持統(tǒng)一的資源加載。(實現(xiàn)了 ResourcePatternResolver 接口)
    3)支持消息機制/應(yīng)用事件肋乍。(實現(xiàn)了 ApplicationEventPublisher 接口)
    4)支持AOP功能鹅颊。



Spring IoC 容器的初始化

Bean的定義和初始化,在Spring IoC 容器中是兩個步驟:先定義(BeanDefinition)墓造,然后初始化和依賴注入堪伍。

  • Bean的定義分為以下3步

1)Resouce定位
Spring IoC 容器根據(jù)開發(fā)者的配置,掃描資源觅闽,進行定位杠娱。在 Spring 的開發(fā)中,通過XML配置或者注解都是常見的方式谱煤,定位的內(nèi)容是由開發(fā)者提供的摊求。

2)BeanDefinition載入
當(dāng)Resoure定位到信息后,保存到Bean定義(BeanDefinition)中刘离,此時并不會創(chuàng)建Bean的實例室叉。

3)BeanDefinition注冊
這個過程,就是將 BeanDefinition 的信息發(fā)布到 Spring IoC 容器中硫惕。注意:此時依然沒有Bean對應(yīng)的實例茧痕。

完成了以上三個步驟,Bean 就在 Spring IoC 容器中被定義了(創(chuàng)建了 Bean 對象的定義類 BeanDefinition恼除,將 Bean 元素的信息設(shè)置到 BeanDefinition 中作為記錄)踪旷,而 沒有被初始化曼氛,更沒有完成依賴注入,也就是沒有注入到其配置的資源給Bean令野,此時舀患,它還不能完全被使用。當(dāng)依賴注入時气破,才利用這些記錄信息聊浅,創(chuàng)建和實例化具體的Bean對象


Spring IoC 容器的依賴注入

依賴注入發(fā)生的時間

當(dāng) Spring IoC 容器完成了Bean定義資源的定位现使,載入和解析注冊以后低匙。IoC容器已經(jīng)管理了Bean定義的相關(guān)數(shù)據(jù),但是此時IoC容器還沒有對所管理的Bean進行依賴注入碳锈,依賴注入在以下兩種情況發(fā)生:

1) 用戶第一次通過 getBean 方法向IoC容器索要Bean時顽冶,IoC容器會觸發(fā)依賴注入。

2) 用戶在Spring的配置選項 <Bean> 元素配置了 lazy-init 的屬性售碳。其含義是:懶加載的方式初始化Bean强重。在沒有配置的情況下,它的默認(rèn)值為default团滥,在default情況下就是false竿屹。也就是說 Spring IoC 默認(rèn)會自動初始化Bean报强。即讓容器在解析注冊Bean定義時灸姊,進行預(yù)實例化,觸發(fā)依賴注入秉溉。

依賴注入的策略

在 Spring 中力惯,如果 Bean 的作用域定義的是單例模式(Singleton),則容器在創(chuàng)建之前會先從緩存中查找召嘶,以確保整個容器中只存在一個實例對象父晶。如果Bean定義的事原型模式(Prototype),則容器每次都會創(chuàng)建一個新的實例對象弄跌。
此外甲喝,在Spring MVC 中,還可以指定作用域為 Request铛只, Session 和 global Session埠胖。(此部分內(nèi)容在下一期細(xì)講)

實例化方式的選擇

對使用工廠方法和自動裝備特性的 Bean 的實例化,調(diào)用相應(yīng)的工廠方法或者參數(shù)匹配的構(gòu)造方法即可完成實例化工作淳玩,但是對于我們最常使用的默認(rèn)無參構(gòu)造方法直撤,就需要使用相應(yīng)的初始化策略(JDK反射或者CGLIB)來進行初始化。

具體來說蜕着,如果Bean的方法被覆蓋了谋竖,則使用JDK反射機制進行實例化,否則。使用CGLIB進行實例化蓖乘。(這是由JDK代理和CGLIB代理方式?jīng)Q定的锤悄,JDK代理是生成了接口的一個實現(xiàn)類,但是如果沒有接口則無法使用)



Spring IoC 高級特性

1)lazy-init 延遲加載

在前面我們了解到驱敲,Spring容器的初始化和Bean的依賴注入是分開的铁蹈。Spring容器在初始化的時候,會對Bean資源進行定位众眨,載入握牧,和注冊。但是完成這三步以后娩梨,Spring并不會對Bean進行依賴注入沿腰。

但是Spring默認(rèn)配置下,在Spring容器初始化完成以后狈定,會對Bean資源依賴注入颂龙,這樣可以及時發(fā)現(xiàn)Bean存在的問題,當(dāng)然我們也推薦這樣做纽什。(雖然延遲加載可以減少服務(wù)器啟動時間措嵌,但是這樣就無法在開始時候就把問題暴露出來)

下面說一下延遲加載的設(shè)置:
Spring 根節(jié)點 <beans> 節(jié)點提供了一個配置參數(shù) default-lazy-init 默認(rèn)為false。也就是說芦缰,默認(rèn)時候是不進行延遲加載的企巢,這樣的話,在 Spring 容器初始化的時候让蕾,就會對bean進行注入浪规。如果設(shè)置為true,那整個<beans>下面的節(jié)點默認(rèn)進行延遲加載探孝。

<beans>下的<bean>節(jié)點也有一個配置參數(shù)lazy-init笋婿,默認(rèn)情況下為default 也就是<beans>上面的設(shè)置的default-lazy-init。如果在這里設(shè)置了的話顿颅,優(yōu)先級比default-lazy-init高缸濒,指定Bean延遲加載。


2)FactoryBean 的實現(xiàn)

FactoryBean 接口粱腻,以Bean結(jié)尾庇配,表示它是一個Bean。注意要和BeanFactory區(qū)別栖疑。

我們根據(jù)Bean的ID讨永,從BeanFactory中獲取的對象,實際上是FactoryBean接口getObject()方法返回的對象遇革。

源碼如下:

//工廠Bean卿闹,用于產(chǎn)生其他對象  
public interface FactoryBean<T> {  
   //獲取容器管理的對象實例  
    T getObject() throws Exception;  
    //獲取Bean工廠創(chuàng)建的對象的類型  
    Class<?> getObjectType();  
    //Bean工廠創(chuàng)建的對象是否是單態(tài)模式揭糕,如果是單態(tài)模式,則整個容器中只有一個實例  
   //對象锻霎,每次請求都返回同一個實例對象  
    boolean isSingleton();  
} 

FactoryBean接口的作用著角,就是讓我們可以封裝自己定制的實例化邏輯。(如果想用工廠模式來實例化)然后讓Spring進行統(tǒng)一管理旋恼。

具體方式就是吏口,我們寫一個實現(xiàn)類,實現(xiàn)FactoryBean的方法冰更,那么我們就可以在getObject()方法里實現(xiàn)我們的自定義實例化邏輯产徊。


3)BeanPostProcessor 后置處理器

BeanPostProcessor 接口作用:
如果我們想在Spring容器中,完成Bean實例化蜀细,配置舟铜,以及其他初始化方法前后,加一些自定義處理邏輯奠衔。那就要自己定義BeanPostProcessor 實現(xiàn)類谆刨,然后注冊到 Spring IoC 容器中。
此處注意:BeanPostProcessor的作用范圍是整個Spring容器

Spring源碼如下:

package org.springframework.beans.factory.config;  
import org.springframework.beans.BeansException;  
public interface BeanPostProcessor {  
        //實例化归斤、依賴注入完畢痊夭,在調(diào)用顯示的初始化之前完成一些定制的初始化任務(wù)
        Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;  
        //實例化、依賴注入脏里、初始化完畢時執(zhí)行  
        Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;  
} 

這兩個回調(diào)的入口都是和容器管理的Bean的生命周期事件緊密相關(guān)她我,可以為用戶提供在 Spring IoC 容器初始化Bean過程中自定義的處理操作。
由API可以看出:
1)后置處理器的postProcessorBeforeInitailization方法膝宁,是在容器實例化Bean(實例化和初始化的區(qū)別:實例化是為Bean對象在內(nèi)存中開辟空間鸦难,初始化是完成對依賴屬性的注入根吁,通過setter等方式)员淫,完成依賴的注入之后;顯示的調(diào)用初始化方法之前調(diào)用(afterPropertiesSet和init-method之前)击敌。
2)后置處理器的postProcessorAfterInitailization方法是在bean實例化介返、依賴注入及自定義初始化方法之后調(diào)用。

調(diào)用順序簡單示意如下:

--> Spring IOC容器實例化Bean
--> 調(diào)用BeanPostProcessor的postProcessBeforeInitialization方法
--> 調(diào)用bean實例的初始化方法
--> 調(diào)用BeanPostProcessor的postProcessAfterInitialization方法

具體使用方式:

//自定義后置處理器
public class MyPostProcessor implements BeanPostProcessor {
    //do something

    @Override
    Object postProcessBeforeInitialization(...){...}

    @Override
    Object postProcessAfterInitialization(...){...}
}

//然后將自定義的后置處理器配置到xml文件中
<!-- Spring后置處理器 -->
<bean id="myPostProcessor" class="com.test.spring.MyPostProcessor "/>

如果要定義多個BeanPostProcessor實現(xiàn)類沃斤,在xml中依次注冊即可圣蝎,默認(rèn)情況下Spring會依據(jù)后置處理器的順序依次調(diào)用。

<bean id="postProcessor" class="com.test.spring.PostProcessor"/>
<bean id="postProcessorB" class="com.test.spring.PostProcessorB"/>

Bean的構(gòu)造方法衡瓶,BeanPostProcessor 和 InitializingBean @PostConstruct @PreDestory 的執(zhí)行關(guān)系徘公。在Bean的生命周期中細(xì)講。


4)IoC容器 @Autowired 自動裝配

@Autowired 注解提供了 Spring IoC 容器的依賴自動裝配功能哮针,不需要對Bean屬性的依賴關(guān)系在配置文件中顯示聲明关面。通過配置該注解坦袍,Spring容器會通過反射根據(jù)類型查找并注入。
(如果有相同類型等太,則需要加一個@Qualifier("beanName")根據(jù)beanName查找指定的Bean)

原理:Spring利用反射捂齐,獲取到標(biāo)記了@Autowired的方法或者參數(shù),然后調(diào)用AutowiredAnnotationBeanPostProcessor中的方法缩抡,對參數(shù)進行注入奠宜。(具體源碼就補貼了,可以去自行搜索瞻想,理解反射即可)

@Autowried 注解可以用于 字段或者 setter上压真。也可以用于構(gòu)造方法和普通方法上(前提是方法至少有一個參數(shù))。不過并不建議作用到普通方法上蘑险,因為Spring會在初始化該Bean時就調(diào)用該方法榴都。

@Autowired 自動裝配功能搭配其他注解使用 例如:@Component,@Controller漠其、@Service嘴高、@Repository,等和屎。
注意要在Spring配置文件里打開注解拴驮,配置掃描路徑:

<!-- 支持注解配置 -->
<context:annotation-config/>

<!--自動掃描所有注解路徑, 只掃描服務(wù) 排除Controller -->
<context:component-scan base-package="com.antony.springdemo">
        <context:exclude-filter type="regex" expression=".*Controller$" />
</context:component-scan>

<!-- 把標(biāo)記了@Controller注解的類轉(zhuǎn)換為bean,單獨轉(zhuǎn)換控制層 -->
<context:component-scan base-package="com.antony.springdemo" use-default-filters="false">
        <!-- 后綴匹配 -->
        <context:include-filter type="regex" expression=".*Controller$"/>
</context:component-scan>

@Autowired 和 @Resource 注解的相同和區(qū)別

  • 兩者都可以用來裝配Bean柴信,都可以用在字段上或者setter上套啤。

  • @Autowired 默認(rèn)按類型裝配,這個注解屬于Spring随常。
    默認(rèn)情況下要求依賴對象必須存在潜沦。如果要允許null值,可以設(shè)置其屬性required=false绪氛。例如:@Autowired(required=false)唆鸡。如果想指定名稱。
    可以配合@Qualifier注解來使用枣察。用于相同類型注入了多個Bean時進行區(qū)分争占。@Qualifier("beanName")

  • @Resource 默認(rèn)按名稱裝配,是JDK1.6支持的注解序目。名稱可以根據(jù)name屬性指定臂痕。
    如果沒有指定name屬性,默認(rèn)取字段名按名稱查找猿涨,當(dāng)使用字段名找不到時握童,按類型查找。
    注意:如果顯式指定了name屬性@Resource(name="beanName")叛赚,那就只會按name指定的名稱進行匹配澡绩。



(如果有什么錯誤或者建議片效,歡迎留言指出)
(本文內(nèi)容是對各個知識點的轉(zhuǎn)載整理,用于個人技術(shù)沉淀英古,以及大家學(xué)習(xí)交流用)


參考資料:
簡書——Spring IOC詳解
源碼解讀Spring IOC原理
Spring IoC的原理
Spring原理機制

Spring IoC容器高級特性
Spring BeanFactory和FactoryBean區(qū)別
Spring中后置處理器BeanPostProcessor詳解
BeanPostProcessor與InitializingBean接口的關(guān)系和應(yīng)用
@Autowired和@Resource注解的區(qū)別
基于Annotation的依賴注入實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淀衣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子召调,更是在濱河造成了極大的恐慌膨桥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唠叛,死亡現(xiàn)場離奇詭異只嚣,居然都是意外死亡,警方通過查閱死者的電腦和手機艺沼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門册舞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人障般,你說我怎么就攤上這事调鲸。” “怎么了挽荡?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵藐石,是天一觀的道長。 經(jīng)常有香客問我定拟,道長于微,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任青自,我火速辦了婚禮株依,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘延窜。我一直安慰自己恋腕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布需曾。 她就那樣靜靜地躺著涩禀,像睡著了一般男图。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磺送,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天车份,我揣著相機與錄音谋减,去河邊找鬼。 笑死扫沼,一個胖子當(dāng)著我的面吹牛出爹,可吹牛的內(nèi)容都是我干的庄吼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼严就,長吁一口氣:“原來是場噩夢啊……” “哼总寻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起梢为,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤渐行,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铸董,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祟印,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年粟害,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕴忆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡悲幅,死狀恐怖套鹅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汰具,我是刑警寧澤芋哭,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站郁副,受9級特大地震影響减牺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜存谎,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一拔疚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧既荚,春花似錦稚失、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晴叨,卻和暖如春凿宾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兼蕊。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工初厚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孙技。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓产禾,卻偏偏與公主長得像排作,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亚情,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,501評論 25 707
  • 轉(zhuǎn)角 文/蒼洱小小生 擁擠的人群妄痪,錯亂的街道 我在茫茫人海中尋找 尋找有你的轉(zhuǎn)角 你的容顏 文/蒼洱小小生 我想用...
    蒼洱小小生閱讀 371評論 2 4
  • 在之前的章節(jié)中,爬取的都是靜態(tài)頁面中的信息楞件,隨著越來越多的網(wǎng)站開始用JS在客戶端瀏覽器動態(tài)渲染網(wǎng)站拌夏,導(dǎo)致很多需要的...
    小怪聊職場閱讀 8,181評論 0 11
  • 今天是2018年的元旦障簿,2018來了!昨天已經(jīng)過去栅迄,明天還沒有到來站故,珍惜今天!一切從今天開始毅舆! 新的工作還沒有消息...
    青青河邊草_366a閱讀 193評論 0 1
  • 巳二西篓、辨住學(xué)(分十六科)午一、不樂利敬【若有成就如是諸法憋活;愛樂正法愛樂功德岂津。】這是第二科悦即。這個「沙門」的「莊嚴(yán)」是...
    德虔閱讀 229評論 0 1