最近學習spring框架,對依賴注入有些模糊,遂上網翻閱資料,做了下列總結,原博客為CSDN 南夏的 spring的依賴注入是什么意思,侵刪!
Spring 能有效地組織J2EE應用各層的對象。不管是控制層的Action對象,還是業務層的Service對象,還是持久層的DAO對象,都可在Spring的 管理下有機地協調、運行。Spring將各層的對象以松耦合的方式組織在一起,Action對象無須關心Service對象的具體實現,Service對 象無須關心持久層對象的具體實現,各層對象的調用完全面向接口。當系統需要重構時,代碼的改寫量將大大減少。
上面所說的一切都得宜於Spring的核心機制,依賴注入。依賴注入讓bean與bean之間以配置文件組織在一起,而不是以硬編碼的方式耦合在一起。理解依賴注入
依賴注入(Dependency Injection)和控制反轉(Inversion of Control)是同一個概念。具體含義是:當某個角色(可能是一個Java實例,調用者)需要另一個角色(另一個Java實例,被調用者)的協助時,在 傳統的程序設計過程中,通常由調用者來創建被調用者的實例。但在Spring里,創建被調用者的工作不再由調用者來完成,因此稱為控制反轉;創建被調用者 實例的工作通常由Spring容器來完成,然后注入調用者,因此也稱為依賴注入。
不管是依賴注入,還是控制反轉,都說明Spring采用動態、靈活的方式來管理各種對象。對象與對象之間的具體實現互相透明。在理解依賴注入之前,看如下這個問題在各種社會形態里如何解決:一個人(Java實例,調用者)需要一把斧子(Java實例,被調用者)。
(1)原始社會里,幾乎沒有社會分工。需要斧子的人(調用者)只能自己去磨一把斧子(被調用者)。對應的情形為:Java程序里的調用者自己創建被調用者。
(2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠里被生產出來,此時需要斧子的人(調用者)找到工廠,購買斧子,無須關心斧子的制造過程。對應Java程序的簡單工廠的設計模式。
(3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。
第一種情況下,Java實例的調用者創建被調用的Java實例,必然要求被調用的Java類出現在調用者的代碼里。無法實現二者之間的松耦合。
第二種情況下,調用者無須關心被調用者具體實現過程,只需要找到符合某種標准(接口)的實例,即可使用。此時調用的代碼面向接口編程,可以讓調用者和被調用者解耦,這也是工廠模式大量使用的原因。但調用者需要自己定位工廠,調用者與特定工廠耦合在一起。
第三種情況下,調用者無須自己定位工廠,程序運行到需要被調用者時,系統自動提供被調用者實例。事實上,調用者和被調用者都處於Spring的管理下,二者之間的依賴關系由Spring提供。
所謂依賴注入,是指程序運行過程中,如果需要調用另一個對象協助時,無須在代碼中創建被調用者,而是依賴於外部的注入。Spring的依賴注入對調用者和被調用者幾乎沒有任何要求,完全支持對POJO之間依賴關系的管理。依賴注入通常有兩種:
·設值注入。
·構造注入。
上面所說的一切都得宜於Spring的核心機制,依賴注入。依賴注入讓bean與bean之間以配置文件組織在一起,而不是以硬編碼的方式耦合在一起。理解依賴注入
依賴注入(Dependency Injection)和控制反轉(Inversion of Control)是同一個概念。具體含義是:當某個角色(可能是一個Java實例,調用者)需要另一個角色(另一個Java實例,被調用者)的協助時,在 傳統的程序設計過程中,通常由調用者來創建被調用者的實例。但在Spring里,創建被調用者的工作不再由調用者來完成,因此稱為控制反轉;創建被調用者 實例的工作通常由Spring容器來完成,然后注入調用者,因此也稱為依賴注入。
不管是依賴注入,還是控制反轉,都說明Spring采用動態、靈活的方式來管理各種對象。對象與對象之間的具體實現互相透明。在理解依賴注入之前,看如下這個問題在各種社會形態里如何解決:一個人(Java實例,調用者)需要一把斧子(Java實例,被調用者)。
(1)原始社會里,幾乎沒有社會分工。需要斧子的人(調用者)只能自己去磨一把斧子(被調用者)。對應的情形為:Java程序里的調用者自己創建被調用者。
(2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠里被生產出來,此時需要斧子的人(調用者)找到工廠,購買斧子,無須關心斧子的制造過程。對應Java程序的簡單工廠的設計模式。
(3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。
第一種情況下,Java實例的調用者創建被調用的Java實例,必然要求被調用的Java類出現在調用者的代碼里。無法實現二者之間的松耦合。
第二種情況下,調用者無須關心被調用者具體實現過程,只需要找到符合某種標准(接口)的實例,即可使用。此時調用的代碼面向接口編程,可以讓調用者和被調用者解耦,這也是工廠模式大量使用的原因。但調用者需要自己定位工廠,調用者與特定工廠耦合在一起。
第三種情況下,調用者無須自己定位工廠,程序運行到需要被調用者時,系統自動提供被調用者實例。事實上,調用者和被調用者都處於Spring的管理下,二者之間的依賴關系由Spring提供。
所謂依賴注入,是指程序運行過程中,如果需要調用另一個對象協助時,無須在代碼中創建被調用者,而是依賴於外部的注入。Spring的依賴注入對調用者和被調用者幾乎沒有任何要求,完全支持對POJO之間依賴關系的管理。依賴注入通常有兩種:
·設值注入。
·構造注入。
設值注入
設值注入是指通過setter方法傳入被調用者的實例。這種注入方式簡單、直觀,因而在Spring的依賴注入里大量使用。看下面代碼,是Person的接口
設值注入是指通過setter方法傳入被調用者的實例。這種注入方式簡單、直觀,因而在Spring的依賴注入里大量使用。看下面代碼,是Person的接口
//定義Person接口 public interface Person { //Person接口里定義一個使用斧子的方法 public void useAxe(); } |
然后是Axe的接口
//定義Axe接口 public interface Axe { //Axe接口里有個砍的方法 public void chop(); } |
Person的實現類
//Chinese實現Person接口 public class Chinese implements Person { //面向Axe接口編程,而不是具體的實現類 private Axe axe; //默認的構造器 public Chinese() {} //設值注入所需的setter方法 public void setAxe(Axe axe) { this.axe = axe; } //實現Person接口的useAxe方法 public void useAxe() { System.out.println(axe.chop()); } } |
Axe的第一個實現類
//Axe的第一個實現類 StoneAxe public class StoneAxe implements Axe { //默認構造器 public StoneAxe() {} //實現Axe接口的chop方法 public String chop() { return "石斧砍柴好慢"; } } |
下面采用Spring的配置文件將Person實例和Axe實例組織在一起。配置文件如下所示:
<!-- 下面是標准的XML文件頭 --> <?xml version="1.0" encoding="gb2312"?> <!-- 下面一行定義Spring的XML配置文件的dtd --> "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- 以上三行對所有的Spring配置文件都是相同的 --> <!-- Spring配置文件的根元素 --> <BEANS> <!—定義第一bean,該bean的id是chinese, class指定該bean實例的實現類 --> <BEAN class="lee".Chinese id=chinese> <!-- property元素用來指定需要容器注入的屬性,axe屬性需要容器注入此處是設值注入,因此Chinese類必須擁有setAxe方法 --> |
<property name="axe"> <!-- 此處將另一個bean的引用注入給chinese bean --> <REF local="”stoneAxe”/"> </property> </BEAN> <!-- 定義stoneAxe bean --> <BEAN class="lee".StoneAxe id=stoneAxe /> </BEANS> |
從配置文件中,可以看到Spring管理bean的靈巧性。bean與bean之間的依賴關系放在配置文件里組織,而不是寫在代碼里。通過配置文件的 指定,Spring能精確地為每個bean注入屬性。因此,配置文件里的bean的class元素,不能僅僅是接口,而必須是真正的實現類。
Spring會自動接管每個bean定義里的property元素定義。Spring會在執行無參數的構造器后、創建默認的bean實例后,調用對應 的setter方法為程序注入屬性值。property定義的屬性值將不再由該bean來主動創建、管理,而改為被動接收Spring的注入。
每個bean的id屬性是該bean的惟一標識,程序通過id屬性訪問bean,bean與bean的依賴關系也通過id屬性完成。
下面看主程序部分:
public class BeanTest { //主方法,程序的入口 public static void main(String[] args)throws Exception { //因為是獨立的應用程序,顯式地實例化Spring的上下文。 ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml"); //通過Person bean的id來獲取bean實例,面向接口編程,因此 //此處強制類型轉換為接口類型 Person p = (Person)ctx.getBean("chinese"); //直接執行Person的userAxe()方法。 p.useAxe(); } } |
程序的執行結果如下:
石斧砍柴好慢
主程序調用Person的useAxe()方法時,該方法的方法體內需要使用Axe的實例,但程序里沒有任何地方將特定的Person實例和Axe實 例耦合在一起。或者說,程序里沒有為Person實例傳入Axe的實例,Axe實例由Spring在運行期間動態注入。
Person實例不僅不需要了解Axe實例的具體實現,甚至無須了解Axe的創建過程。程序在運行到需要Axe實例的時候,Spring創建了Axe 實例,然后注入給需要Axe實例的調用者。Person實例運行到需要Axe實例的地方,自然就產生了Axe實例,用來供Person實例使用。
調用者不僅無須關心被調用者的實現過程,連工廠定位都可以省略(真是按需分配啊!)。下面也給出使用Ant編譯和運行該應用的簡單腳本:
<?xml version="1.0"?> <!-- 定義編譯該項目的基本信息--> <PROJECT name="spring" default="." basedir="."> <!-- 定義編譯和運行該項目時所需的庫文件 --> <PATH id=classpath> <!-- 該路徑下存放spring.jar和其他第三方類庫 --> <FILESET dir=../../lib> <INCLUDE name="*.jar" /> </FILESET> <!-- 同時還需要引用已經編譯過的class文件--> <PATHELEMENT path="." /> </PATH> <!-- 編譯全部的java文件--> <TARGET description="Compile all source code" name="compile"> <!-- 指定編譯后的class文件的存放位置 --> <JAVAC debug="true" destdir="."> deprecation="false" optimize="false" failonerror="true"> <!-- 指定需要編譯的源文件的存放位置 --> <SRC path="." /> <!-- 指定編譯這些java文件需要的類庫位置--> <CLASSPATH refid="classpath" /> </JAVAC> </TARGET> <!-- 運行特定的主程序 --> <TARGET description="run the main class" name="run" depends="compile"> <!-- 指定運行的主程序:lee.BeanTest。--> <JAVA failonerror="true" fork="yes" classname="lee.BeanTest"> <!-- 指定運行這些java文件需要的類庫位置--> <CLASSPATH refid="classpath" /> </JAVA> </TARGET> </PROJECT> |
如果需要改寫Axe的實現類。或者說,提供另一個實現類給Person實例使用。Person接口、Chinese類都無須改變。只需提供另一個Axe的實現,然后對配置文件進行簡單的修改即可。
Axe的另一個實現如下:
//Axe的另一個實現類 SteelAxe public class SteelAxe implements Axe { //默認構造器 public SteelAxe() {} //實現Axe接口的chop方法 public String chop() { return "鋼斧砍柴真快"; } } |
然后,修改原來的Spring配置文件,在其中增加如下一行:
<!-- 定義一個steelAxe bean--> <BEAN class="lee".SteelAxe id=steelAxe /> |
該行重新定義了一個Axe的實現:SteelAxe。然后修改chinese bean的配置,將原來傳入stoneAxe的地方改為傳入steelAxe。也就是將
<REF local="”stoneAxe”/"> |
改成
<REF local="”steelAxe”/"> |
此時再次執行程序,將得到如下結果:
鋼斧砍柴真快
Person與Axe之間沒有任何代碼耦合關系,bean與bean之間的依賴關系由Spring管理。采用setter方法為目標bean注入屬性的方式,稱為設值注入。
業務對象的更換變得相當簡單,對象與對象之間的依賴關系從代碼里分離出來,通過配置文件動態管理。
構造注入
所謂構造注入,指通過構造函數來完成依賴關系的設定,而不是通過setter方法。對前面代碼Chinese類做簡單的修改,修改后的代碼如下:
所謂構造注入,指通過構造函數來完成依賴關系的設定,而不是通過setter方法。對前面代碼Chinese類做簡單的修改,修改后的代碼如下:
//Chinese實現Person接口 public class Chinese implements Person { //面向Axe接口編程,而不是具體的實現類 private Axe axe; //默認的構造器 public Chinese() {} //構造注入所需的帶參數的構造器 public Chinse(Axe axe) { this.axe = axe; } //實現Person接口的useAxe方法 public void useAxe() { System.out.println(axe.chop()); } } |
此時無須Chinese類里的setAxe方法,構造Person實例時,Spring為Person實例注入所依賴的Axe實例。構造注入的配置文件也需做簡單的修改,修改后的配置文件如下:
<!-- 下面是標准的XML文件頭 --> <xml version="1.0" encoding="gb2312"?> <!-- 下面一行定義Spring的XML配置文件的dtd --> "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- 以上三行對所有的Spring配置文件都是相同的 --> <!-- Spring配置文件的根元素 --> <BEANS> <!—定義第一個bean,該bean的id是chinese, class指定該bean實例的實現類 --> <BEAN class="lee".Chinese id=chinese> </BEAN> <!-- 定義stoneAxe bean --> <BEAN class="lee".SteelAxe id=steelAxe /> </BEANS> |
執行效果與使用steelAxe設值注入時的執行效果完全一樣。區別在於:創建Person實例中Axe屬性的時機不同——設值注入是現創建一個默認的bean實例,然后調用對應的構造方法注入依賴關系。而構造注入則在創建bean實例時,已經完成了依賴關系的