傳統應用程序設計中所說的依賴通常指「類之間的關係」,那先讓咱們複習一下類之間的關係:html
泛化:表示類與類之間的繼承關係、接口與接口之間的繼承關係;java
實現:表示類對接口的實現;spring
依賴:當類與類之間有使用關係時就屬於依賴關係,不一樣於關聯關係,依賴不具備「擁有關係」,而是一種「相識關係」,只在某個特定地方(好比某個方法體內)纔有關係。編程
關聯:表示類與類或類與接口之間的依賴關係,表現爲「擁有關係」;具體到代碼能夠用實例變量來表示;ide
聚合:屬因而關聯的特殊狀況,體現部分-總體關係,是一種弱擁有關係;總體和部分能夠有不同的生命週期;是一種弱關聯;測試
組合:屬因而關聯的特殊狀況,也體現了體現部分-總體關係,是一種強「擁有關係」;總體與部分有相同的生命週期,是一種強關聯;this
Spring IoC容器的依賴有兩層含義:Bean依賴容器和容器注入Bean的依賴資源:spa
Bean依賴容器:也就是說Bean要依賴於容器,這裏的依賴是指容器負責建立Bean並管理Bean的生命週期,正是因爲由容器來控制建立Bean並注入依賴,也就是控制權被反轉了,這也正是IoC名字的由來,此處的有依賴是指Bean和容器之間的依賴關係。.net
容器注入Bean的依賴資源:容器負責注入Bean的依賴資源,依賴資源能夠是Bean、外部文件、常量數據等,在Java中都反映爲對象,而且由容器負責組裝Bean之間的依賴關係,此處的依賴是指Bean之間的依賴關係,能夠認爲是傳統類與類之間的「關聯」、「聚合」、「組合」關係。設計
爲何要應用依賴注入,應用依賴注入能給咱們帶來哪些好處呢?
動態替換Bean依賴對象,程序更靈活:替換Bean依賴對象,無需修改源文件:應用依賴注入後,因爲能夠採用配置文件方式實現,從而能隨時動態的替換Bean的依賴對象,無需修改java源文件;
更好實踐面向接口編程,代碼更清晰:在Bean中只需指定依賴對象的接口,接口定義依賴對象完成的功能,經過容器注入依賴實現;
更好實踐優先使用對象組合,而不是類繼承:由於IoC容器採用注入依賴,也就是組合對象,從而更好的實踐對象組合。
採用對象組合,Bean的功能可能由幾個依賴Bean的功能組合而成,其Bean自己可能只提供少量功能或根本無任何功能,所有委託給依賴Bean,對象組合具備動態性,能更方便的替換掉依賴Bean,從而改變Bean功能;
而若是採用類繼承,Bean沒有依賴Bean,而是採用繼承方式添加新功能,,並且功能是在編譯時就肯定了,不具備動態性,並且採用類繼承致使Bean與子Bean之間高度耦合,難以複用。
增長Bean可複用性:依賴於對象組合,Bean更可複用且複用更簡單;
下降Bean之間耦合:因爲咱們徹底採用面向接口編程,在代碼中沒有直接引用Bean依賴實現,所有引用接口,並且不會出現顯示的建立依賴對象代碼,並且這些依賴是由容器來注入,很容易替換依賴實現類,從而下降Bean與依賴之間耦合;
代碼結構更清晰:要應用依賴注入,代碼結構要按照規約方式進行書寫,從而更好的應用一些最佳實踐,所以代碼結構更清晰。
從以上咱們能夠看出,其實依賴注入只是一種裝配對象的手段,設計的類結構纔是基礎,若是設計的類結構不支持依賴注入,Spring IoC容器也注入不了任何東西,從而從根本上說「如何設計好類結構纔是關鍵,依賴注入只是一種裝配對象手段」。
前邊IoC一章咱們已經瞭解了Bean依賴容器,那容器如何注入Bean的依賴資源,Spring IoC容器注入依賴資源主要有如下兩種基本實現方式:
構造器注入:就是容器實例化Bean時注入那些依賴,經過在在Bean定義中指定構造器參數進行注入依賴,包括實例工廠方法參數注入依賴,但靜態工廠方法參數不容許注入依賴;
setter注入:經過setter方法進行注入依賴;
方法注入:能經過配置方式替換掉Bean方法,也就是經過配置改變Bean方法 功能。
咱們已經知道注入實現方式了,接下來讓咱們來看看具體配置吧。
使用構造器注入經過配置構造器參數實現,構造器參數就是依賴。除了構造器方式,還有靜態工廠、實例工廠方法能夠進行構造器注入。如圖3-1所示:
圖3-1 實例化
構造器注入能夠根據參數索引注入、參數類型注入或Spring3支持的參數名注入,但參數名注入是有限制的,須要使用在編譯程序時打開調試模式(即在編譯時使用「javac –g:vars」在class文件中生成變量調試信息,默認是不包含變量調試信息的,從而能獲取參數名字,不然獲取不到參數名字)或在構造器上使用@ConstructorProperties(java.beans.ConstructorProperties)註解來指定參數名。
首先讓咱們準備測試構造器類HelloImpl3.java,該類只有一個包含兩個參數的構造器:
java代碼:
package cn.javass.spring.chapter3.helloworld;
public class HelloImpl3 implements HelloApi {
private String message;
private int index;
//@java.beans.ConstructorProperties({"message", "index"})
public HelloImpl3(String message, int index) {
this.message = message;
this.index = index;
}
@Override
public void sayHello() {
System.out.println(index + ":" + message);
}
}
1、根據參數索引注入,使用標籤「<constructor-arg index="1" value="1"/>」來指定注入的依賴,其中「index」表示索引,從0開始,即第一個參數索引爲0,「value」來指定注入的常量值,配置方式以下:
2、根據參數類型進行注入,使用標籤「<constructor-arg type="java.lang.String" value="Hello World!"/>」來指定注入的依賴,其中「type」表示須要匹配的參數類型,能夠是基本類型也能夠是其餘類型,如「int」、「java.lang.String」,「value」來指定注入的常量值,配置方式以下:
3、根據參數名進行注入,使用標籤「<constructor-arg name="message" value="Hello World!"/>」來指定注入的依賴,其中「name」表示須要匹配的參數名字,「value」來指定注入的常量值,配置方式以下:
4、讓咱們來用具體的例子來看一下構造器注入怎麼使用吧。
(1)首先準備Bean類,在此咱們就使用「HelloImpl3」這個類。
(2)有了Bean類,接下來要進行Bean定義配置,咱們須要配置三個Bean來完成上述三種依賴注入測試,其中Bean 」byIndex」是經過索引注入依賴;Bean 」byType」是根據類型進行注入依賴;Bean 」byName」是根據參數名字進行注入依賴,具體配置文件(resources/chapter3/ constructorDependencyInject.xml)以下:
java代碼:
<!-- 經過構造器參數索引方式依賴注入 -->
<bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean>
<!-- 經過構造器參數類型方式依賴注入 -->
<bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3">
<constructor-arg type="java.lang.String" value="Hello World!"/>
<constructor-arg type="int" value="2"/>
</bean>
<!-- 經過構造器參數名稱方式依賴注入 -->
<bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3">
<constructor-arg name="message" value="Hello World!"/>
<constructor-arg name="index" value="3"/>
</bean>
(3)配置完畢後,在測試以前,由於咱們使用了經過構造器參數名字注入方式,請確保編譯時class文件包含「變量信息」,具體查看編譯時是否包含「變量調試信息」請右擊項目,在彈出的對話框選擇屬性;而後在彈出的對話框選擇「Java Compiler」條目,在「Class 文件 生成」框中選擇「添加變量信息到Class文件(調試器使用)」,具體如圖3-2:
圖3-2 編譯時打開「添加變量信息選項」
(4)接下來讓咱們測試一下配置是否工做,具體測試代碼(cn.javass.spring.chapter3. DependencyInjectTest)以下:
java代碼:
public void testConstructorDependencyInjectTest() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter3/constructorDependencyInject.xml");
//獲取根據參數索引依賴注入的Bean
HelloApi byIndex = beanFactory.getBean("byIndex", HelloApi.class);
byIndex.sayHello();
//獲取根據參數類型依賴注入的Bean
HelloApi byType = beanFactory.getBean("byType", HelloApi.class);
byType.sayHello();
//獲取根據參數名字依賴注入的Bean
HelloApi byName = beanFactory.getBean("byName", HelloApi.class);
byName.sayHello();
}
經過以上測試咱們已經會基本的構造器注入配置了,在測試經過參數名字注入時,除了可使用以上方式,還能夠經過在構造器上添加@java.beans.ConstructorProperties({"message", "index"})註解來指定參數名字,在HelloImpl3構造器上把註釋掉的「ConstructorProperties」打開就能夠了,這個就留給你們作練習,本身配置而後測試一下。
5、你們已經會了構造器注入,那讓咱們再看一下靜態工廠方法注入和實例工廠注入吧,其實它們注入配置是徹底同樣,在此咱們只示範一下靜態工廠注入方式和實例工廠方式配置,測試就留給你們本身練習:
(1)靜態工廠類
java代碼:
//靜態工廠類
package cn.javass.spring.chapter3;
import cn.javass.spring.chapter2.helloworld.HelloApi;
public class DependencyInjectByStaticFactory {
public static HelloApi newInstance(String message, int index) {
return new HelloImpl3(message, index);
}
}
靜態工廠類Bean定義配置文件(chapter3/staticFactoryDependencyInject.xml)
java代碼:
<bean id="byIndex"
class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean>
<bean id="byType"
class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg type="java.lang.String" value="Hello World!"/>
<constructor-arg type="int" value="2"/>
</bean>
<bean id="byName"
class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">
<constructor-arg name="message" value="Hello World!"/>
<constructor-arg name="index" value="3"/>
</bean>
(2)實例工廠類
java代碼:
//實例工廠類
package cn.javass.spring.chapter3;
import cn.javass.spring.chapter2.helloworld.HelloApi;
public class DependencyInjectByInstanceFactory {
public HelloApi newInstance(String message, int index) {
return new HelloImpl3(message, index);
}
}
實例工廠類Bean定義配置文件(chapter3/instanceFactoryDependencyInject.xml)
java代碼:
<bean id="instanceFactory"
class="cn.javass.spring.chapter3.DependencyInjectByInstanceFactory"/>
<bean id="byIndex"
factory-bean="instanceFactory" factory-method="newInstance">
<constructor-arg index="0" value="Hello World!"/>
<constructor-arg index="1" value="1"/>
</bean>
<bean id="byType"
factory-bean="instanceFactory" factory-method="newInstance">
<constructor-arg type="java.lang.String" value="Hello World!"/>
<constructor-arg type="int" value="2"/>
</bean>
<bean id="byName"
factory-bean="instanceFactory" factory-method="newInstance">
<constructor-arg name="message" value="Hello World!"/>
<constructor-arg name="index" value="3"/>
</bean>
(3)測試代碼和構造器方式徹底同樣,只是配置文件不同,你們只需把測試文件改一下就能夠了。還有一點須要你們注意就是靜態工廠方式和實例工廠方式根據參數名字注入的方式只支持經過在class文件中添加「變量調試信息」方式才能運行,ConstructorProperties註解方式不能工做,它只對構造器方式起做用,不建議使用根據參數名進行構造器注入。
setter注入,是經過在經過構造器、靜態工廠或實例工廠實例好Bean後,經過調用Bean類的setter方法進行注入依賴,如圖3-3所示:
圖3-3 setter注入方式
setter注入方式只有一種根據setter名字進行注入:
知道配置方式了,接下來先讓咱們來作個簡單例子吧。
(1)準備測試類HelloImpl4,須要兩個setter方法「setMessage」和「setIndex」:
java代碼:
package cn.javass.spring.chapter3;
import cn.javass.spring.chapter2.helloworld.HelloApi;
public class HelloImpl4 implements HelloApi {
private String message;
private int index;
//setter方法
public void setMessage(String message) {
this.message = message;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public void sayHello() {
System.out.println(index + ":" + message);
}
}
(2)配置Bean定義,具體配置文件(resources/chapter3/setterDependencyInject.xml)片斷以下:
java代碼:
<!-- 經過setter方式進行依賴注入 -->
<bean id="bean" class="cn.javass.spring.chapter3.HelloImpl4">
<property name="message" value="Hello World!"/>
<property name="index">
<value>1</value>
</property>
</bean>
(3)該寫測試進行測試一下是否知足能工做了,其實測試代碼一點沒變,變的是配置:
java代碼:
@Test
public void testSetterDependencyInject() {
BeanFactory beanFactory =
new ClassPathXmlApplicationContext("chapter3/setterDependencyInject.xml");
HelloApi bean = beanFactory.getBean("bean", HelloApi.class);
bean.sayHello();
}
知道如何配置了,但Spring如何知道setter方法?如何將值注入進去的呢?其實方法名是要遵照約定的,setter注入的方法名要遵循「JavaBean getter/setter 方法命名約定」:
JavaBean:是本質就是一個POJO類,但具備一下限制:
該類必需要有公共的無參構造器,如public HelloImpl4() {};
屬性爲private訪問級別,不建議public,如private String message;
屬性必要時經過一組setter(修改器)和getter(訪問器)方法來訪問;
setter方法,以「set」 開頭,後跟首字母大寫的屬性名,如「setMesssage」,簡單屬性通常只有一個方法參數,方法返回值一般爲「void」;
getter方法,通常屬性以「get」開頭,對於boolean類型通常以「is」開頭,後跟首字母大寫的屬性名,如「getMesssage」,「isOk」;
還有一些其餘特殊狀況,好比屬性有連續兩個大寫字母開頭,如「URL」,則setter/getter方法爲:「setURL」和「getURL」,其餘一些特殊狀況請參看「Java Bean」命名規範。