IoC
IoC:Inversion of Control(控制反轉)
Spring是一個包含了眾多工具的IoC容器(即bean:spring管理的對象),也就是說Spring 是一個“控制反轉”的容器。
之前是對象本身管理自己的生命周期等等,現在交給spring來管理對象的生命周期
IoC介紹
1.2.1 傳統程序開發
傳統程序開發:一個對象(類)的創建,通常由調用者(使用者)來負責創建的,比如我們創建一個Car
對象,通常由Framework
來創建,Framework
創建Car
對象時,如果Car
對象需要依賴其他對象(比如Bottom
,或者Tire
),那么這些依賴對象也需要Framework
來創建,并注入給Car
對象。
傳統程序開發流程示意圖:
傳統程序開發代碼示例:
public class NewCarExample {public static void main(String[] args) {// 傳統方式:直接創建Car對象Car car = new Car();car.run();}
}// Car.java
public class Car {private Framework framework;public Car() {// Car類內部負責創建其依賴的Framework對象framework = new Framework();System.out.println("Car init...");}public void run() {System.out.println("Car run...");framework.run();}
}// Framework.java
public class Framework {private Bottom bottom;public Framework() {// Framework類內部負責創建其依賴的Bottom對象bottom = new Bottom();System.out.println("Framework init...");}public void run() {System.out.println("Framework run...");bottom.run();}
}// Bottom.java
public class Bottom {private Tire tire;public Bottom() {// Bottom類內部負責創建其依賴的Tire對象this.tire = new Tire();System.out.println("Bottom init...");}public void run() {System.out.println("Bottom run...");tire.run();}
}// Tire.java
public class Tire {private int size;public Tire() {this.size = 17;System.out.println("輪胎尺寸: " + size);}public void run() {System.out.println("Tire run...");}
}
1.2.2 問題分析
在上面的代碼中,我們發現Car
對象對Framework
類、Bottom
類、Tire
類有很強的依賴關系。如果這些依賴關系中的任何一個發生變化,比如Car
類中需要增加一個依賴對象,或者某個依賴對象的創建方式發生變化(比如Tire
需要進行自定義的Size,那么構造方法就需要修改,導致依賴他的所有方法的構造函數都需要修改),那么Car
類也需要進行修改。這使得代碼的耦合度很高,不利于代碼的維護和擴展。
具體來說,我們看以下幾點:
Framework
類對Car
類的依賴:Framework
類在main
方法中直接創建了Car
對象。如果Car
類的構造函數發生變化,Framework
類也需要修改。這同樣導致了高耦合。- 依賴的傳遞性: 如果
Car
類又依賴了其他類,那么這些其他類也需要Framework
來創建,并注入給Car
對象。這樣一來,Framework
類就成了整個應用程序的“大管家”,它需要知道所有對象的創建細節,這使得Framework
類變得臃腫且難以維護。 - 單元測試困難: 由于
Car
類直接創建了Framework
對象,當對Car
類進行單元測試時,很難替換掉Framework
的真實實現(例如,使用模擬對象)。這使得單元測試變得復雜。 - 代碼復用性差: 由于
Car
類與其需要依賴的類緊密耦合,Car
類很難在其他不包含這些被依賴的類的場景下復用。
1.2.3 解決方案
在上面的解決方案中,我們看到針對傳統程序設計模式的缺點,我們可以引入IoC(Inversion of Control) 思想,即控制反轉。IoC的核心思想是:對象的創建和依賴關系的維護不再由調用者負責,而是由一個外部的容器來負責。當調用者需要某個對象時,不再自己去創建,而是向容器請求,容器會負責創建并提供給調用者。
這就像是:以前你餓了,需要自己去廚房做飯(自己創建對象),現在你餓了,只需要告訴餐廳你要什么菜(向容器請求對象),餐廳會幫你做好并送過來(容器創建并提供對象)。這樣,你就不需要關心做飯的細節,只需要關心吃什么。
IoC控制反轉流程示意圖:
通過IoC,我們實現了控制反轉,將對象的創建和依賴注入的控制權從應用程序代碼中移出,交給了IoC容器。這樣,應用程序代碼與它所依賴的對象之間的耦合度就大大降低了。
IoC容器就像一個中央工廠,負責管理所有對象的生命周期和依賴關系。當應用程序需要某個對象時,只需向IoC容器聲明其需求,IoC容器就會負責創建該對象及其所有依賴,并將其注入到應用程序中。
IoC容器示意圖:
IoC容器的好處有:
- 降低耦合度: 對象之間不再直接依賴,而是通過IoC容器進行解耦。當一個類的依賴發生變化時,不需要修改該類本身的代碼,只需要修改IoC容器的配置。這使得代碼更加靈活,易于維護和擴展。
- 提高代碼復用性: 對象不再負責創建自己的依賴,因此可以更容易地在不同的場景下復用。例如,
Car
類不再其依賴類的創建細節,它可以與任何實現了其依賴類的接口的對象一起使用。 - 提高可測試性: 在單元測試中,可以輕松地替換掉真實的對象,使用模擬對象進行測試。這使得單元測試更加簡單和高效。
- 簡化配置: IoC容器可以集中管理所有對象的創建和依賴關系,從而簡化了應用程序的配置。開發人員不再需要手動管理大量的對象創建代碼。
1.2.4 IoC程序開發
基于以上思想,我們嘗試用代碼來表示IoC到底是怎么一回事,特別注意受控方式,因為是IoC的關鍵所在。
首先我們先修改一下:
// NewCarExample.java
public class NewCarExample {public static void main(String[] args) {// IoC方式:由外部(模擬IoC容器)創建并注入依賴Tire tire = new Tire();Bottom bottom = new Bottom(tire); // Bottom依賴TireFramework framework = new Framework(bottom); // Framework依賴BottomCar car = new Car(framework); // Car依賴Frameworkcar.run();}
}// Car.java
public class Car {private Framework framework;// 通過構造函數注入依賴public Car(Framework framework) {this.framework = framework;System.out.println("Car create...");}public void run() {System.out.println("Car run...");framework.run();}
}// Bottom.java
public class Bottom {private Tire tire;// 通過構造函數注入依賴public Bottom(Tire tire) {this.tire = tire;System.out.println("Bottom create...");}public void run() {System.out.println("Bottom run...");tire.run();}
}// Tire.java
public class Tire {public Tire() {System.out.println("Tire create...");}public void run() {System.out.println("Tire run...");}
}// Framework.java
public class Framework {private Bottom bottom;// 通過構造函數注入依賴public Framework(Bottom bottom) {this.bottom = bottom;System.out.println("Framework create...");}public void run() {System.out.println("Framework run...");bottom.run();}
}
代碼如上圖所示,我們看到,各個類之間不再直接依賴,而是通過構造器注入的方式,將依賴對象從外部傳入。這使得各個類之間的耦合度大大降低了,并且可以非常容易地進行替換(模擬或真實對象)。當然離真正的IoC容器還有很長的路要走,但思想已經非常接近了。
現在我們再來理一下,Car
依賴Framework
,Framework
依賴Bottom
,Bottom
依賴Tire
,那么整個依賴關系是:
依賴關系與IoC容器示意圖:
IoC容器是一個獨立的模塊,它負責創建和管理所有的對象。當一個對象需要另一個對象時,它不再自己去創建,而是向IoC容器請求。IoC容器會負責創建所需的對象,并將它們注入到請求對象中,創建實例的時候不需要了解其中的細節, 降低了使用雙方的的依賴程度,這樣,對象之間就解耦了。
Framework
、Bottom
、Tire
和Car
現在都變成了“被動”的對象,它們不再主動去創建自己的依賴對象,而是等待IoC容器將依賴對象注入進來。這種“被動”的特性就是IoC的核心思想。
IoC容器就像一個中央工廠,負責管理所有對象的生命周期和依賴關系。當應用程序需要某個對象時,只需向IoC容器聲明其需求,IoC容器就會負責創建該對象及其所有依賴,并將其注入到應用程序中。
DI
DI: Dependency Injection(依賴注入) 容器在運行期間,動態的為應用程序提供運行時所依賴的資源,稱之為依賴注入。
從這一點來看,依賴注入(DI)和控制逆轉(IoC)是從不同的角度所描述的同一件事,依賴注入是
從應用程序的角度來描述,指通過引入IoC容器,利用依賴關系注入的方式,實現對象之間的解耦
簡單使用
@Component
的作用
@Component
就像給你的類貼上一個“標簽”,告訴Spring: “嘿,Spring!我是一個組件,請你管理我,并且在需要的時候,可以把我的實例提供給別人。”
當Spring掃描到帶有@Component
注解的類時,它就會:
- 創建并管理這個類的實例(對象)。
- 將這個實例放入它的“容器”中,隨時準備被其他地方使用。
@Autowired
的作用
@Autowired
就像一個“請求”,告訴Spring: “嘿,Spring!我這里需要一個你管理的某個類型的對象(比如一個BookDao
),請你把它“送”給我!”
Spring收到這個請求后,就會從它管理的眾多組件中找到一個匹配的,然后自動把它賦值給你的變量。
結合 @Component
和 @Autowired
的DI流程
-
定義組件(
@Component
):@Component // 告訴Spring:我是BookDao,請你管理我 public class BookDao {// ... 提供數據的方法 }@Component // 告訴Spring:我是BookService,請你管理我 public class BookService {// ... 處理業務邏輯的方法 }
現在,BookDao
和 BookService
的實例都由Spring創建和管理了。
-
注入依賴(
@Autowired
):@Component public class BookService {@Autowired // 告訴Spring:我需要一個BookDao,請你給我private BookDao bookDao; // Spring會自動把BookDao的實例賦值給它// ... }@RestController // @RestController也包含了@Component的功能,所以Spring也會管理它 public class BookController {@Autowired // 告訴Spring:我需要一個BookService,請你給我private BookService bookService; // Spring會自動把BookService的實例賦值給它// ... }
BookService
不再自己new BookDao()
,而是聲明它需要一個BookDao
,Spring會注入進來。BookController
不再自己new BookService()
,而是聲明它需要一個BookService
,Spring會注入進來。
最終效果: 各個類(BookController
、BookService
、BookDao
)之間不再直接創建對方的實例,而是通過Spring這個“中間人”來獲取它們需要的依賴。這使得代碼:
- 更松散:類與類之間不再緊密耦合。
- 更靈活:可以輕松替換依賴的實現。
- 更容易測試:測試時可以注入模擬的依賴。
深入介紹文章:
IoC詳細介紹: here
DI詳細介紹:here