Spring的DI和IOC IOC實現方式
一.依賴注入和控制反轉 依賴注入(Dependecy Injection)和控制反轉(Inversion of Control)是同一個概念,具體的講:當某個角色 需要另外一個角色協助的時候,在傳統的程式設計過程中,通常由呼叫者來建立被呼叫者的例項。但在spring中 建立被呼叫者的工作不再由呼叫者來完成,因此稱為控制反轉。建立被呼叫者的工作由spring來完成,然後注入呼叫者 因此也稱為依賴注入。 自己理解:即一句話,由spring容器來控制組件A的呼叫的具體物件B。元件A依賴於spring容器的注入。 二.依賴注入好處 依賴注入的好處。 不管是依賴注入,還是控制反轉,都說明Spring採用動態、靈活的方式來管理各種物件。物件與物件之間的具體實現互相透明。在理解依賴注入之前,看如下這個問題在各種社會形態裡如何解決:一個人(Java例項,呼叫者)需要一把斧子(Java例項,被呼叫者)。 (1)原始社會裡,幾乎沒有社會分工。需要斧子的人(呼叫者)只能自己去磨一把斧子(被呼叫者)。對應的情形為:Java程式裡的呼叫者自己建立被呼叫者。 (2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠裡被生產出來,此時需要斧子的人(呼叫者)找到工廠,購買斧子,無須關心斧子的製造過程。對應Java程式的簡單工廠的設計模式。 (此方法依賴於介面) (3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家裡發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。 第一種情況下,Java例項的呼叫者建立被呼叫的Java例項,必然要求被呼叫的Java類出現在呼叫者的程式碼裡。無法實現二者之間的鬆耦合。 第二種情況下,呼叫者無須關心被呼叫者具體實現過程,只需要找到符合某種標準(介面)的例項,即可使用。此時呼叫的程式碼面向介面程式設計,可以讓呼叫者和被呼叫者解耦,這也是工廠模式大量使用的原因。但呼叫者需要自己定位工廠,呼叫者與特定工廠耦合在一起。 情況下,呼叫者無須自己定位工廠,程式執行到需要被呼叫者時,系統自動提供被呼叫者例項。事實上,呼叫者和被呼叫者都處於Spring的管理下,二者之間的依賴關係由Spring提供。 延時載入:容器在依賴注入讓Spring的Bean以被指檔案組織在一起,而不是以硬編碼的方式耦合在一起。程式完成無須理會被呼叫者的實現,也不無須主動定位工廠,這是最好的解耦方式。例項之間的依賴關係由IoC容器負責管理。 三.IOC實現方式 IOC:也就是控制反轉(建立物件例項的控制權反轉),說的是一個類A要呼叫另一個類B,本來應該在類A裡面建立B的例項的,控制權在A手裡。現在用了Spring了,有了IOC,控制權就不在A手裡了,而是交到Spring的IOC容器了,A要用到B,那Spring就把B分給A了
package com.spring.demo.entity; /** * 一個偉大的程式設計師類 * */ public class Programmer { private String name; private String sex; public void coding(){ //要用到computer物件,呼叫computer的coding方法, //在這裡new computer物件,控制權在Programmer手裡 Computer computer = new Computer(); computer.coding(); } }
package com.spring.demo.entity; public class Computer { private String brand; private String color; private double size; public void coding() { System.out.println("Computer is coding!!!"); } } 上面,就是用傳統方式建立依賴物件。接下來,看看Spring IOC是怎樣反轉控制權來建立物件的。
方式一:屬性注入(setter注入)
package com.spring.demo02.entity;
public class Programmer {
private String name;
private String sex;
// 在這裡定義要依賴的computer屬性,加上set方法
private Computer computer;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Computer getComputer() {
return computer;
}
/**
* 加上Setter方法
* */
public void setComputer(Computer computer) {
this.computer = computer;
}
}
package com.spring.demo02.entity;
public class Computer {
private String brand;
private String color;
private String size;
public void coding() {
System.out.println("Computer is coding!!!");
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
}
看上面的程式碼,可以發現,Programmer類裡面,有3個屬性,name,sex,computer,並且都有對應的getter、setter方法;Computer類裡面也有三個屬性,分別是品牌、顏色、尺寸,也都有對應的getter、setter方法。這只是第一步,在類裡面宣告屬性並且實現set方法。 接下來,要寫一個spring的xml配置檔案,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="programmer" class="com.spring.demo2.entity.Programmer">
<property name="name" value="小李"></property>
<property name="sex" value="男"></property>
<property name="computer" ref="computer"></property>
</bean>
<bean id="computer" class="com.spring.demo2.entity.Computer">
<property name="brand" value="hp"></property>
<property name="color" value="黑"></property>
<property name="size" value="14"></property>
</bean>
</beans>
解讀一下這個xml檔案: 1.宣告一個bean,可以理解為例項化了一個物件。那這裡例項化了兩個物件(programmer和computer),各個屬性都已經賦值上去。
2.id為programmer的bean,其實就是Programmer類;通過給property賦值,Spring就會通過Programmer類的各個屬性的set方法,逐一給Programmer的屬性賦值。
3.在programmer裡面,有一個屬性是computer的,可以看到它屬性值是 ref=“computer”,這就說明computer這個屬性是個引用,這裡ref後面的值其實就是指向另一個bean的id值,所以這裡引用的是id為computer的bean。 以上,就是屬性注入了。關鍵的是在類裡面宣告屬性,寫set方法,然後在xml裡面配置bean和property的值 方式二、構造器注入 構造器注入,顧名思義,就是在構造器裡面注入依賴物件。那是怎麼實現的呢?其實跟屬性注入差不多,定義一個有參構造器,然後配置xml檔案就行了。看程式碼:
package com.spring.demo03.entity;
import com.spring.demo02.entity.Computer;
public class Programmer {
private Computer computer;
public Programmer(Computer computer){
this.computer = computer;
}
}
package com.spring.demo03.entity;
public class Computer {
private String brand;
private String color;
private String size;
public Computer(String brand, String color, String size) {
this.brand = brand;
this.color = color;
this.size = size;
}
}
上面兩個類都有一個有參的構造器,接下來,在xml裡面配置這兩個bean,然後再配置構造器的引數值就可以了
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="programmer" class="com.spring.demo3.entity.Programmer">
<constructor-arg ref="computer"></constructor-arg>
</bean>
<!-- 構造器裡面沒有name欄位,只有value,是根據構造器的方法引數順序來定義的 -->
<bean id="computer" class="com.spring.demo3.entity.Computer">
<constructor-arg value="聯想"></constructor-arg>
<constructor-arg value="紅色"></constructor-arg>
<constructor-arg value="15.6寸"></constructor-arg>
</bean>
</beans>
方式三:自動裝配
package com.spring.demo04.entity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Programmer {
@Autowired
Computer computer;
}
package com.spring.demo04.entity;
import org.springframework.stereotype.Component;
@Component
public class Computer {
private String brand;
private String color;
private String size;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<context:component-scan base-pakage="com.spring.demo04">
</beans>
關鍵點:在類前面加註解:@Component,在需要注入的類裡面加註解:@Autowired,這樣xml裡面的自動掃描就會掃描到這些加了註解的類和屬性,在例項化bean的時候,Spring容器會把加了@Component的類例項化;在實際執行時,會給加了@Autowired的屬性注入對應的例項。 Spring中的自動裝配有哪些限制? 如果使用了構造器注入或者setter注入,那麼將覆蓋自動裝配的依賴關係。 基本資料型別的值、字串字面量、類字面量無法使用自動裝配來注入。 有先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。 和自動裝配相關的註解有哪些? @Required:該依賴關係必須裝配(手動或自動裝配),否則將丟擲BeanInitializationException異常。 @Autowired:自動裝配,預設按型別進行自動裝配。 @Qualifier:如果按型別自動裝配時有不止一個匹配的型別,那麼可以使用該註解指定名字來消除歧義。