一、IOC概念 上一篇已經瞭解了spring的相關概念,並且創建了一個spring項目。spring中有最重要的兩個概念:IOC和AOP,我們先從IOC入手。 IOC全稱Inversion of Control,中文通常翻譯為“控制反轉”,這其實不是一種技術,而是一種思想。 簡單理解就是把原先我們代 ...
上一篇已經瞭解了spring的相關概念,並且創建了一個spring項目。spring中有最重要的兩個概念:IOC和AOP,我們先從IOC入手。
IOC全稱Inversion of Control,中文通常翻譯為“控制反轉”,這其實不是一種技術,而是一種思想。
簡單理解就是把原先我們代碼裡面需要實現的對象創建、依賴的代碼,反轉給容器來幫忙實現。
這裡分享Iteye的開濤對Ioc的精彩講解
地址:https://jinnianshilongnian.iteye.com/blog/1413846
IoC是什麼
Ioc—Inversion of Control,即“控制反轉”,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。如何理解好Ioc呢?理解好Ioc的關鍵是要明確“誰控制誰,控制什麼,為何是反轉(有反轉就應該有正轉了),哪些方面反轉了”,那我們來深入分析一下:
●誰控制誰,控制什麼:傳統Java SE程式設計,我們直接在對象內部通過new進行創建對象,是程式主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對 象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
●為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及註入依賴對象;為何是反轉?因為由容器幫我們查找及註入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。
用圖例說明一下,傳統程式設計如圖1-1,都是主動去創建相關對象然後再組合起來:
圖1-1 傳統應用程式示意圖
當有了IoC/DI的容器後,在客戶端類中不再主動去創建這些對象了,如圖2-2所示:
圖1-2有IoC/DI容器後程式結構示意圖
IoC能做什麼
IoC 不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程式。傳統應用程式都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把創建和查找依賴對象的控制權交給了容器,由容器進行註入組合對象,所以對象與對象之間是 鬆散耦合,這樣也方便測試,利於功能復用,更重要的是使得程式的整個體繫結構變得非常靈活。
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程式原本是老大,要獲取什麼資源都是主動出擊,但是在IoC/DI思想中,應用程式就變成被動的了,被動的等待IoC容器來創建並註入它所需要的資源了。
IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對象找相應的依賴對象並註入,而不是由對象主動去找。
IoC和DI
DI—Dependency Injection,即“依賴註入”:組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係註入到組件之中。依賴註入的目的並非為軟體系統帶來更多功能,而是為了提升組件重用的頻率,併為系統搭建一個靈活、可擴展的平臺。通過依賴註入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰註入誰,註入了什麼”,那我們來深入分析一下:
●誰依賴於誰:當然是應用程式依賴於IoC容器;
●為什麼需要依賴:**應用程式需要IoC容器來提供對象需要的外部資源**;
●誰註入誰:很明顯是IoC容器註入應用程式某個對象,應用程式依賴的對象;
●註入了什麼:就是註入某個對象所需要的外部資源(包括對象、資源、常量數據)。
IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制對象這一個層面,很難讓人想到誰來維護對象關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴註入”,相對IoC 而言,“**依賴註入”明確描述了“被註入對象依賴IoC容器配置依賴對象”。
構造方法註入
顧名思義,構造方法註入,就是被註入對象可以通過在其構造方法中聲明依賴對象的參數列表, 讓外部(通常是IoC容器)知道它需要哪些依賴對象
public classA(IinterfaceA a,IinterfaceB b){ this.a=a; this.b=b; }
構造方法註入方式比較直觀,對象被構造完成後,即進入就緒狀態,可以馬上使用。
setter 方法註入
對於JavaBean對象來說,通常會通過setXXX()和getXXX()方法來訪問對應屬性。這些setXXX()方法統稱為setter方法,getXXX()當然就稱為getter方法。
public class classB(){ private IinterfaceA a; private IinterfaceB b; public IinterfaceA getIinterfaceA(){ return a; } public void setIinterfaceA(IinterfaceA a){ this.a=a; } public IinterfaceB getIinterfaceB(){ return b; } public void setIinterfaceB(IinterfaceB b){ this.b=b; } }
介面註入
相對於前兩種註入方式來說,介面註入沒有那麼簡單明瞭。被註入對象如果想要IoC Service Provider為其註入依賴對象,就必須實現某個介面。這個介面提供一個方法,用來為其註入依賴對象。IoC Service Provider最終通過這些介面來瞭解應該為被註入對象註入什麼依賴對象。
創建Person (被註入對象)要實現的介面
interface UserInject{ void injectUser(User user);//這裡必須 是被註入對象依賴的對象 }
Person 對象實現介面
class Person implements UserInject{ private User user; public Person(){} @Override public void injectUser(User user) { this.user = user;//實現註入方法,外部通過此方法給此對象註入User對象 } }
外部調injectUser方法為Persion對象註入User對象,此即介面註入
三種註入方式的比較
-
介面註入。從註入方式的使用上來說,介面註入是現在不甚提倡的一種方式,基本處於“退役狀態”。因為它強制被註入對象實現不必要的介面,帶有侵入性。而構造方法註入和setter方法註入則不需要如此。
-
構造方法註入。這種註入方式的優點就是,對象在構造完成之後,即已進入就緒狀態,可以 9馬上使用。缺點就是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而通過反射構造對象的時候,對相同類型的參數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設置預設值。對於非必須的依賴處理,可能需要引入多個構造方法,而參數數量的變動可能造成維護上的不便。
-
setter方法註入。因為方法可以命名,所以setter方法註入在描述性上要比構造方法註入好一些。 另外,setter方法可以被繼承,允許設置預設值,而且有良好的IDE支持。缺點當然就是對象無法在構造完成後馬上進入就緒狀態。
綜上所述,構造方法註入和setter方法註入因為其侵入性較弱,且易於理解和使用,所以是現在使用最多的註入方式;而介面註入因為侵入性較強,近年來已經不流行了。
二、源碼分析
在學習spring的具體配置之前,先瞭解下源碼的基本結構。上一篇的測試代碼
ApplicationContext ctx=new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); //獲取bean的實例 HelloWorld t=(HelloWorld) ctx.getBean("hello");
我們大致分析下過程:
-
通過Resource對象載入配置文件
-
解析配置文件,得到bean
-
解析bean,id作為bean的名字,class用於反射得到bean的實例(Class.forName(className));
-
當然這隻是簡單的理解,IOC核心內容是beanFactory與ApplicationContext
BeanFactory
BeanFactory 是 Spring 的“心臟”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 來實例化、配置和管理 Bean,BeanFactory有著龐大的繼承、實現體系,有眾多的子介面、實現類。
-
BeanFactory作為一個主介面不繼承任何介面,暫且稱為一級介面。
-
有3個子介面繼承了它,進行功能上的增強。這3個子介面稱為二級介面。
-
ConfigurableBeanFactory可以被稱為三級介面,對二級介面HierarchicalBeanFactory進行了再次增強,它還繼承了另一個外來的介面SingletonBeanRegistry
-
ConfigurableListableBeanFactory是一個更強大的介面,繼承了上述的所有介面,無所不包,稱為四級介面。(這4級介面是BeanFactory的基本介面體系。繼續,下麵是繼承關係的2個抽象類和2個實現類:)
-
AbstractBeanFactory作為一個抽象類,實現了三級介面ConfigurableBeanFactory大部分功能。
-
AbstractAutowireCapableBeanFactory同樣是抽象類,繼承自AbstractBeanFactory,並額外實現了二級介面AutowireCapableBeanFactory
-
DefaultListableBeanFactory繼承自AbstractAutowireCapableBeanFactory,實現了最強大的四級介面ConfigurableListableBeanFactory,並實現了一個外來介面BeanDefinitionRegistry,它並非抽象類。
-
最後是最強大的XmlBeanFactory,繼承自DefaultListableBeanFactory,重寫了一些功能,使自己更強大。
public interface BeanFactory { /** * 用來引用一個實例,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory */ String FACTORY_BEAN_PREFIX = "&"; /* * 四個不同形式的getBean方法,獲取實例 */ //根據bean的名字,獲取在IOC容器中得到bean實例 Objecpublic interface BeanFactory { /** * 用來引用一個實例,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory */ String FACTORY_BEAN_PREFIX = "&"; /* * 四個不同形式的getBean方法,獲取實例 */ //根據bean的名字,獲取在IOC容器中得到bean實例 Object getBean(String name) throws BeansException; //根據bean的名字和Class類型來得到bean實例,增加了類型安全驗證機制。 <T> T getBean(String name, Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; // 是否存在 boolean containsBean(String name); // 是否為單實例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; // 是否為原型(多實例) boolean isPrototype(String name) throws NoSuchBeanDefinitionException; // 名稱、類型是否匹配 boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException; //得到bean實例的Class類型 Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name);// 根據實例的名字獲取實例的別名 getBean(String name) throws BeansException; //根據bean的名字和Class類型來得到bean實例,增加了類型安全驗證機制。 <T> T getBean(String name, Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; // 是否存在 boolean containsBean(String name); // 是否為單實例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; // 是否為原型(多實例) boolean isPrototype(String name) throws NoSuchBeanDefinitionException; // 名稱、類型是否匹配 boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException; //得到bean實例的Class類型 Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name);// 根據實例的名字獲取實例的別名
BeanFactory介面只是做了最基本的定義,裡面不管如何定義和載入,只關心如何得到對象,要知道如何得到對象,必須看具體的實現類,其中XmlBeanFactory就是針對最基本的ioc容器的實現。
public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader; public XmlBeanFactory(Resource resource) throws BeansException { this(resource, (BeanFactory)null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader = new XmlBeanDefinitionReader(this); this.reader.loadBeanDefinitions(resource); } }
使用:
//根據Xml配置文件創建Resource資源對象,該對象中包含了BeanDefinition的信息 Resource resource = new ClassPathResource("META-INF/applicationContext.xml"); //創建XmlBeanDefinitionReader讀取器,用於載入BeanDefinition。之所以需要BeanFactory作為參數,是因為會將讀取的信息回調配置給factory BeanFactory beanFactory = new XmlBeanFactory(resource); HelloWorld helloWorld = beanFactory.getBean("hello",HelloWorld.class); System.out.println(helloWorld.getInfo());
ApplicationContext
ApplicationContext是Spring提供的一個高級的IoC容器,它除了能夠提供IoC容器的基本功能外,還為用戶提供了以下的附加服務。
-
-
訪問資源。(實現ResourcePatternResolver介面)
-
支持應用事件。(實現ApplicationEventPublisher介面)
兩者的區別
1.BeanFactroy採用的是延遲載入形式來註入Bean的,即只有在使用到某個Bean時(調用getBean()),才對該Bean進行載入實例化,這樣,我們就不能發現一些存在的Spring的配置問題。而ApplicationContext則相反,它是在容器啟動時,一次性創建了所有的Bean。這樣,在容器啟動時,我們就可以發現Spring中存在的配置錯誤。 相對於基本的BeanFactory,ApplicationContext 唯一的不足是占用記憶體空間。當應用程式配置Bean較多時,程式啟動較慢。
BeanFacotry延遲載入,如果Bean的某一個屬性沒有註入,BeanFacotry載入後,直至第一次使用調用getBean方法才會拋出異常;而ApplicationContext則在初始化自身是檢驗,這樣有利於檢查所依賴屬性是否註入;所以通常情況下我們選擇使用 ApplicationContext。 應用上下文則會在上下文啟動後預載入所有的單實例Bean。通過預載入單實例bean ,確保當你需要的時候,你就不用等待,因為它們已經創建好了。
2.BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區別是:BeanFactory需要手動註冊,而ApplicationContext則是自動註冊。(Applicationcontext比 beanFactory 加入了一些更好使用的功能。而且 beanFactory 的許多功能需要通過編程實現而 Applicationcontext 可以通過配置實現。比如後處理 bean , Applicationcontext 直接配置在配置文件即可而 beanFactory 這要在代碼中顯示的寫出來才可以被容器識別。 )
3.beanFactory主要是面對與 spring 框架的基礎設施,面對 spring 自己。而 Applicationcontex 主要面對與 spring 使用的開發者。基本都會使用 Applicationcontex 並非 beanFactory 。
看完這些相信對spring IOC概念及其實現會有了一些理性認識了,這裡面參考了很多園子里大神的文字,下一篇開始學習spring的配置