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