ioc——inversion of control,控制反轉
在java開發中,ioc意味著將你設計好的類交給系統去控制,而不是在你的類內部控制。ioc是一種讓服務消費者不直接依賴于服務提供者的組件設計方式,是一種減少類與類之間依賴的設計原則。
di——dependency injection(依賴注入)
即組件之間的依賴關系由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關系注入到組件之中。
依賴注入的目標并非為軟件系統帶來更多的功能,而是為了提升組件重用的概率,并為系統搭建一個靈活、可擴展的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務 邏輯,而不用關心具體的資源來自何處、由誰實現。
1:控制反轉:
誰控制誰?控制什么?為何叫反轉(對應于正向)?哪些方面反轉了 ?為何需要反轉?
2:依賴:
什么是依賴(按名詞理解,按動詞理解)?誰依賴于誰?為什么需要依賴?依賴什么東西?
3:注入:
誰注入于誰?注入什么東西?為何要注入?
4:依賴注入和控制反轉是同一概念嗎?
5:參與者都有哪些?
6:ioc/di是什么?能做什么?怎么做?用在什么地方?
還不能完全回答和理解,沒有關系,先來看看ioc/di的基本思想演變,然后再回頭來回答這些問題
ioc容器
簡單的理解就是:實現ioc思想,并提供對象創建、對象裝配以及對象生命周期管理的軟件就是ioc容器。
ioc理解
1:應用程序無需主動new對象;而是描述對象應該如何被創建即可
ioc容器幫你創建,即被動實例化;
2:應用程序不需要主動裝配對象之間的依賴關系,而是描述需要哪個服務
ioc容器會幫你裝配(即負責將它們關聯在一起),被動接受裝配;
3:主動變被動,體現好萊塢法則:別打電話給我們,我們會打給你
4:體現迪米特法則(最少知識原則):應用程序不知道依賴的具體實現,只知道需要提供某類服務的對象(面向接口編程);并松散耦合,一個對象應當對其他對象有盡可能少的了解,不和陌生人(實現)說話
5:是一種讓服務消費者不直接依賴于服務提供者的組件設計方式,是一種減少類與類之間依賴的設計原則。
使用ioc/di容器開發需要改變的思路
1:應用程序不主動創建對象,但要描述創建它們的方式。
2:在應用程序代碼中不直接進行服務的裝配,但要描述哪一個組件需要哪一項服務,由容器負責將這些裝配在一起。
也就是說:所有的組件都是被動的,組件初始化和裝配都由容器負責,應用程序只是在獲取相應的組件后,實現應用的功能即可。
提醒一點
ioc/di是思想,不是純實現技術。ioc是框架共性,只是控制權的轉移,轉移到框架,所以不能因為實現了ioc就叫ioc容器,而一般除了實現了ioc外,還具有di功能的才叫ioc容器,因為容器除了要負責創建并裝配組件關系,還需要管理組件生命周期。
n工具準備
1:eclipse + jdk6.0 ,示例用的eclipse是eclipse java ee ide for web developers,version: helios service release 1
2:spring-framework-3.1.0.m2-with-docs.zip
構建環境
1:在eclipse里面新建一個工程,設若名稱是spring3test
2:把發行包里面的dist下面的jar包都添加到eclipse里面
3:根據spring的工程來獲取spring需要的依賴包,在聯網的情況下,通過ant運行projects/build-spring-framework/build.xml,會自動去下載所需要的jar包,下載后的包位于projects/ivy-cache/repository下面。
4:為了方便,把這些jar包也添加到eclipse里面
開發接口
java代碼:
1
2
3
|
public interface helloapi { public string hellospring3( int a); } |
開發實現類
java代碼:
1
2
3
4
5
6
|
public class helloimpl implements helloapi{ public string hellospring3( int a){ system.out.println( "hello spring3===" +a); return "ok,a=" +a; } } |
配置文件
1:在src下面新建一個文件叫applicationcontext.xml
2:在spring發行包里面搜索一個例子,比如使用:projects\org.springframework.context\src\test\java\org\springframework\jmx下的applicationcontext.xml,先把里面的配置都刪掉,留下基本的xml定義和根元素就可以了,它是一個dtd版的,而且還是2.0版的。
建議使用spring3的schema版本,示例如下:
java代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?xml version= "1.0" encoding= "utf-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/xmlschema-instance" xmlns:context= "http://www.springframework.org/schema/context" xmlns:aop= "http://www.springframework.org/schema/aop" xmlns:tx= "http://www.springframework.org/schema/tx" xsi:schemalocation=" http: //www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http: //www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http: //www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http: //www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd "> ……………………… </beans> |
4:配置applicationcontext.xml如下:
java代碼:
1
|
<bean name= "hellobean" class = "com.bjpowernode.spring3.hello.helloimpl" ></bean> |
編寫客戶端如下:
java代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.bjpowernode.spring3.hello; import org.springframework.context.applicationcontext; import org.springframework.context.support.classpathxmlapplicationcontext; public class client { public static void main(string[] args) { applicationcontext context = new classpathxmlapplicationcontext( new string[] { "applicationcontext.xml" }); helloapi api = (helloapi)context.getbean( "hellobean" ); string s = api.hellospring3( 3 ); system.out.println( "the s=" +s); } } |
審視和結論
1:所有代碼中(除測試代碼之外),并沒有出現spring的任何組件 。
2:客戶代碼(這里就是我們的測試代碼)僅僅面向接口編程,而無需知道實現類的具體名稱。同時,我們可以很簡單的通過修改配置文件來切換具體的底層實現類 。
結論
1:首先,我們的組件并不需要實現框架指定的接口,因此可以輕松的將組件從spring脫離,甚至不需要任何修改(這在基于ejb架實現的應用中是難以想象的)。
2:其次,組件間的依賴關系減少,極大改善了代碼的可重用性和可維護性
3:面向接口編程
什么是spring中的bean
在spring中,那些組成應用的主體及由spring ioc容器所管理的對象被稱之為bean。簡單地講,bean就是由spring容器初始化、裝配及被管理的對象,除此之外,bean就沒有特別之處了(與應用中的其他對象沒有什么區別)。而bean定義以及bean相互間的依賴關系將通過配置元數據來描述。
為什么使用bean這個名字
使用‘bean'這個名字而不是‘組件'(component) 或‘對象'(object)的動機源于spring框架本身(部分原因則是相對于復雜的ejb而言的)。
spring的ioc容器
org.springframework.beans.factory.beanfactory是spring ioc容器的實際代表者,ioc容器負責容納bean,并對bean進行管理。
spring ioc容器將讀取配置元數據;并通過它對應用中各個對象進行實例化、配置以及組裝。通常情況下我們使用簡單直觀的xml來作為配置元數據的描述格式。在xml配置元數據中我們可以對那些我們希望通過spring ioc容器管理的bean進行定義。
ioc/di是spring最核心的功能之一, spring框架所提供的眾多功能之所以能成為一個整體正是建立在ioc的基礎之上
beanfactory和applicationcontext
org.springframework.beans及org.springframework.context包是spring ioc容器的基礎。beanfactory提供的高級配置機制,使得管理任何性質的對象成為可能。 applicationcontext是beanfactory的擴展,功能得到了進一步增強,比如更易與spring aop集成、消息資源處理(國際化處理)、事件傳遞及各種不同應用層的context實現(如針對web應用的webapplicationcontext)。
接口選擇之惑
在實際應用中,用戶有時候不知道到底是選擇beanfactory接口還是applicationcontext接口。但是通常在構建javaee應用時,使用applicationcontext將是更好的選擇,因為它不僅提供了beanfactory的所有特性,同時也允許使用更多的聲明方式來得到我們想要的功能。
簡而言之,beanfactory提供了配制框架及基本功能,而applicationcontext則增加了更 多支持企業核心內容的功能。applicationcontext完全由beanfactory擴展而來,因而beanfactory所具備的能力和行為也適用于applicationcontext。
spring ioc容器的實例化非常簡單,如下面的例子:
1:第一種:
java代碼:
1
2
|
resource resource = new filesystemresource( "beans.xml" ); beanfactory factory = new xmlbeanfactory(resource); |
2:第二種:
java代碼:
1
2
|
classpathresource resource = new classpathresource( "beans.xml" ); beanfactory factory = new xmlbeanfactory(resource); |
3:第三種:
java代碼:
1
2
3
4
|
applicationcontext context = new classpathxmlapplicationcontext( new string[] { "applicationcontext.xml" , "applicationcontext-part2.xml" }); // of course, an applicationcontext is just a beanfactory beanfactory factory = (beanfactory) context; |
讀取多個配置文件
第一種方法:
為了加載多個xml文件生成一個applicationcontext實例,可以將文件路徑作為字符串數組傳給applicationcontext構造器。而bean factory將通過調用bean defintion reader從多個文件中讀取bean定義。通常情況下,spring團隊傾向于上述做法,因為這樣各個配置并不會查覺到它們與其他配置文件的組合。
第二種方法:
使用一個或多個的<import/>元素來從另外一個或多個文件加載bean定義。所有的<import/>元素必須放在<bean/>元素之前以完成bean定義的導入。 讓我們看個例子:
java代碼:
1
2
3
4
5
6
|
<beans>< import resource= "services.xml" /> < import resource=“/resources/messagesource.xml"/> < import resource= "/resources/themesource.xml" /> <bean id= "bean1" class = "..." /> <bean id= "bean2" class = "..." /> </beans> |
配置文件中常見的配置內容
在ioc容器內部,bean的定義由beandefinition 對象來表示,該定義將包含以下信息:
1:全限定類名:這通常就是已定義bean的實際實現類。如果通過調用static factory方法來實例化bean,而不是使用常規的構造器,那么類名稱實際上就是工廠類的類名。
2:bean行為的定義,即創建模式(prototype還是singleton)、自動裝配模式、依賴檢查模式、初始化以及銷毀方法。這些定義將決定bean在容器中的行為。
3:用于創建bean實例的構造器參數及屬性值。比如使用bean來定義連接池,可以通過屬性或者構造參數指定連接數,以及連接池大小限制等。
4:bean之間的關系,即協作 (或者稱依賴)。
bean的命名
每個bean都有一個或多個id(或稱之為標識符或名稱,在術語上可以理解成一回事),這些id在當前ioc容器中必須唯一。
當然也可以為每個bean定義一個name,但是并不是必須的,如果沒有指定,那么容器將為其生成一個惟一的name。對于不指定name屬性的原因我們會在后面介紹(比如內部bean就不需要)。
bean命名的約定
bean的命名采用標準的java命名約定,即小寫字母開頭,首字母大寫間隔的命名方式。如accountmanager、 accountservice等等。
對bean采用統一的命名約定將會使配置更加簡單易懂。而且在使用spring aop,這種簡單的命名方式將會令你受益匪淺。
bean的別名
一個bean要提供多個名稱,可以通過alias屬性來加以指定 ,示例如下:
1
|
<alias name= "fromname" alias= "toname" /> |
容器如何實例化bean
當采用xml描述配置元數據時,將通過<bean/>元素的class屬性來指定實例化對象的類型。class屬性主要有兩種用途:在大多數情況下,容器將直接通過 反射調 用指定類的構造器來創建bean(這有點等類似于在java代碼中使用new操作符);在極少數情況下,容器將調用類的靜態工廠方法來創建bean實例,class屬性將用來指定實際具有靜態工廠方法的類(至于調用靜態工廠方法創建的對象類型是當前class還是其他的class則無關緊要)。
用構造器來實例化bean ,前面的實例就是
使用靜態工廠方法實例化
采用靜態工廠方法創建bean時,除了需要指定class屬性外,還需要通過factory-method屬性來指定創建bean實例的工廠方法,示例如下:
1
2
3
|
<bean id= "examplebean" class = "examples.examplebean2" factory-method= "createinstance" /> |
使用實例工廠方法實例化
使用此機制,class屬性必須為空,而factory-bean屬性必須指定為當前(或其祖先)容器中包含工廠方法的bean的名稱,而該工廠bean的工廠方法本身必須通過factory-method屬性來設定,并且這個方法不能是靜態的,示例如下:
1
|
<bean id= "examplebean" factory-bean= "myfactorybean" factory-method= "createinstance" /> |
使用容器
從本質上講,beanfactory僅僅只是一個維護bean定義以及相互依賴關系的高級工廠接口。使用getbean(string)方法就可以取得bean的實例;beanfactory提供的方法極其簡單。n依賴注入(di) 背后的基本原理
是對象之間的依賴關系(即一起工作的其它對象)只會通過以下幾種方式來實現: 構造器的參數、 工廠方法的參數,或 給由構造函數或者工廠方法創建的對象設 置屬性。
因此,容器的工作就是創建bean時注入那些依賴關系。相對于由bean自己來控制其實例化、直接在構造器中指定依賴關系或則類似服務定位器(service locator)模式這3種自主控制依賴關系注入的方法來說,控制從根本上發生了倒轉,這也正是控制反轉ioc名字的由來。
應用依賴注入(di)的好處、
應用di原則后,代碼將更加清晰。而且當bean自己不再擔心對象之間的依賴關系(以及在何時何地指定這種依賴關系和依賴的實際類是什么)之后,實現更高層次的 松耦合將易如反掌。
依賴注入(di)基本的實現方式
di主要有兩種注入方式,即 setter注入和 構造器注入。
通過調用無參構造器或無參static工廠方法實例化bean之后,調用該bean的setter方法,即可實現基于setter的di。 示例如下:
示例——java類
java代碼:
1
2
3
4
5
6
7
8
9
10
|
public class helloimpl implements helloapi{ private string name = "" ; public void setname(string name){ this .name = name; } public string hellospring3( int a){ system.out.println( "hello spring3===" +a+ ",name=" +name); return "ok,a=" +a; } } |
示例——配置文件
1
2
3
|
<bean name= "hellobean" class = "com.bjpowernode.spring3.hello.helloimpl" > <property name= "name" ><value>bjpowernode spring3</value></property> </bean> |
示例——java類
java代碼:
1
2
3
4
5
6
7
8
9
10
|
public class helloimpl implements helloapi{ private string name = "" ; public helloimpl(string name){ this .name = name; } public string hellospring3( int a){ system.out.println( "hello spring3===" +a+ ",name=" +name); return "ok,a=" +a; } } |
示例——配置文件
java代碼:
1
2
3
|
<bean name= "hellobean" class = "com.bjpowernode.spring3.hello.helloimpl" > <constructor-arg><value>bjpowernode spring3</value></constructor-arg> </bean> |
默認解析方式
構造器參數將根據類型來進行匹配。如果bean定義中的構造器參數類型明確,那么bean定義中的參數順序就是對應構造器參數的順序
構造器參數類型匹配
可以使用type屬性來顯式的指定參數所對應的簡單類型。例如:
java代碼:
1
2
3
4
|
<bean id= "examplebean" class = "examples.examplebean" > <constructor-arg type= "int" value= "7500000" /> <constructor-arg type= "java.lang.string" value= "42" /> </bean> |
構造器參數的索引
使用index屬性可以顯式的指定構造器參數出現順序。例如:
java代碼:
1
2
3
4
|
<bean id= "examplebean" class = "examples.examplebean" > <constructor-arg index= "0" value= "7500000" /> <constructor-arg index= "1" value= "42" /> </bean> |
構造器參數的名稱
在spring3里面,可以使用構造器參數的名稱來直接賦值。例如:
java代碼:
1
2
3
4
|
<bean id= "examplebean" class = "examples.examplebean" > <constructor-arg name= "years" value= "7500000" /> <constructor-arg name= "ultimateanswer" value= "42" /> </bean> |
直接量(基本類型、strings類型等)
<value/>元素通過字符串來指定屬性或構造器參數的值。javabean屬性編輯器將把字符串從java.lang.string類型轉化為實際的屬性或參數類型。示例:
java代碼:
1
2
3
4
5
6
7
8
9
10
|
<bean id= "mydatasource" class = "org.apache.commons.dbcp.basicdatasource" > <property name= "driverclassname" > <value>oracle.jdbc.driver.oracledriver</value> </property> <property name= "url" > <value>jdbc:oracle:thin: @localhost : 1521 :orcl</value> </property> <property name= "username" > <value>test</value> </property> <property name= "password" value=“test"/> </bean> |
value可以做為子元素或者是屬性使用。
nidref元素
idref元素用來將容器內其它bean的id傳給<constructor-arg/> 或 <property/>元素,同時提供錯誤驗證功能。 idref元素和<value>差不多,只是傳遞 一個字符串,用來方便xml檢查。示例如下:
java代碼:
1
2
3
4
5
6
7
8
9
|
<bean id= "thetargetbean" class = "..." /> <bean id= "theclientbean" class = "..." > <property name= "targetname" > <idref bean= "thetargetbean" /> </property> </bean> 上述bean定義片段完全地等同于(在運行時)以下的片段 <bean id= "thetargetbean" class = "..." /> <bean id= "client" class = "..." > <property name= "targetname" > <value>thetargetbean</value> </property> </bean> |
idref元素 續
第一種形式比第二種更可取的主要原因是,使用idref標記允許容器在部署時 驗證所被引用的bean是否存在。而第二種方式中,傳給client bean的targetname屬性值并沒有被驗證。任何的輸入錯誤僅在client bean實際實例化時才會被發現(可能伴隨著致命的錯誤)。如果client bean 是prototype類型的bean,則此輸入錯誤(及由此導致的異常)可能在容器部署很久以后才會被發現。
如果被引用的bean在同一xml文件內,且bean名字就是bean id,那么可以使用local屬性,此屬性允許xml解析器在解析xml文件時來對引用的bean進行驗證 ,示例如下:
1
2
3
|
<property name= "targetname" > <idref local= "thetargetbean" /> </property> |
引用其它的bean(協作者) ——ref元素
盡管都是對另外一個對象的引用,但是通過id/name指向另外一個對象卻有三種不同的形式,不同的形式將決定如何處理作用域及驗證。
1:第一種形式也是最常見的形式是使用<ref/>標記指定目標bean,示例:
1
|
<ref bean=“somebean”/> |
2:第二種形式是使用ref的local屬性指定目標bean,它可以利用xml解析器來驗證所引用的bean是否存在同一文件中。示例:
1
|
<ref local= "somebean" /> |
3:第三種方式是通過使用ref的parent屬性來引用當前容器的父容器中的bean,并不常用。示例:
java代碼:
1
2
3
4
|
<bean id= "accountservice" class = "com.foo.simpleaccountservice" > </bean> <bean id=“accountservice” <-- 注意這里的名字和parent的名字是一樣的--> class = "org.springframework.aop.framework.proxyfactorybean" > <property name= "target" ><ref parent= "accountservice" /> </property> </bean> |
內部bean
所謂的內部bean(inner bean)是指在一個bean的<property/>或 <constructor-arg/>元素中使用<bean/>元素定義的bean。內部bean定義不需要有id或name屬性,即使指定id 或 name屬性值也將會被容器忽略。示例:
java代碼:
1
2
3
4
5
6
7
8
|
<bean id= "outer" class = "..." > <property name= "target" > <bean class = "com.mycompany.person" > <property name= "name" value= "fiona apple" /> <property name= "age" value= "25" /> </bean> </property> </bean> |
原文鏈接:http://blog.sina.com.cn/s/blog_9c6852670102ww9k.html