Spring IoC(控制反轉)
1. 前言
通過第一章第二小節,我們已經可以使用 Spring 框架實現對自定義的 Java 對象管理,由 Spring 框架加載對象,實例化對象,放入容器。其實這就是 Spirng 的核心功能之 IoC,那么什么是 IoC 呢?什么又是容器呢?
跟我來,一步步揭開他們的神秘面紗。
2. 什么是 IoC?
來自百度百科的解釋 —— 控制反轉(IoC):
(Inversion of Control,縮寫為 IoC),是面向對象編程中的一種設計原則,可以用來降低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱 DI),還有一種方式叫 “依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。
慕課解釋
如何理解好 IoC 呢?上一個小節中,我們使用簡單的語言對它做了一個描述 —— IoC 是一種設計模式。將實例化對象的控制權,由手動的 new 變成了 Spring 框架通過反射機制實例化。
那我們來深入分析一下為什么使用 IoC 做控制反轉,它到底能幫助我們做什么。
我們假設一個場景:
我們在學習 Web 階段的過程中,一定實現過數據的查詢功能,那么這里我就舉一個實例:
我們有這樣幾個類:
- UserServlet
- UserService 接口
- UserServiceImpl 接口的實現類
- UserDao
代碼如下:
/*
UserServlet 作為控制器 接收瀏覽器的請求
*/
public class UserServlet extends HttpServletRequest {
//用戶的業務類 提供邏輯處理 用戶相關的方法實現
private UserService userService;
public void service(HttpServletRequest request,HttpServletResponse response){
//手動實例化UserService接口的實現類
userService = new UserServiceImpl();
List<User> list = userService.findAll();
//省略結果的跳轉代碼
}
}
/*
用戶的業務接口UserService
*/
public interface UserService{
public List<User> findAll();
}
/*
UserServiceImpl 作為用戶的業務實現類 實現類UserService的接口
*/
public class UserServiceImpl implements UserService{
//用戶的Dao
private UserDao userDao;
public List<User> findAll(){
//手動實例化Dao
userDao = new UserDao();
return userDao.findAll();
}
}
問題分析:
上面的代碼有什么問題嗎? 按照我們學習過的知識… 答案是沒有。因為 Dao 只要數據源編寫代碼正確, 完全可以實現數據的增刪改查 ,對嗎?
但是分析分析它我們發現:
-
代碼耦合性太強 不利于程序的測試:
因為userServlet
依賴于userService
,而userService
依賴于userDao
, 那么只要是被依賴的對象,一定要實例化才行。所以我們采取在程序中硬編碼,使用new
關鍵字對對象做實例化。 不利于測試,因為你不能確保所有使用的依賴對象都被成功地初始化了。有的朋友很奇怪,對象實例化有什么問題嗎? 如果構造參數不滿足要求,或者你的構造進行了邏輯處理,那么就有可能實例化失??; -
代碼也不利于擴展:
假設一下,我們花了九牛二虎外加一只雞的力氣,整理好了所有的類使用的依賴,確保不會產生問題,那么一旦后續我們的方法進行擴充,改造了構造函數,或者判斷邏輯,那么是不是所有手動 new 對象的地方都需要更改? 很明顯這就不是一個優雅的設計。
解決方式:
Spring 的 IoC 完美的解決了這一點, 對象的實例化由 Spring 框架加載實現,放到 Spring 的容器中管理,避免了我們手動的 new 對象,有需要用到對象實例依賴,直接向 Spring 容器要即可,而一旦涉及到對象的實例修改,那么 只需更改 Spring 加載實例化對象的地方,程序代碼無需改動,降低了耦合,提升了擴展性。
3. 容器的使用
剛剛我們解釋了 IoC 的作用,是對象的實例化由主動的創建變成了 Spring 的創建,并放入容器管理,那么這個容器是什么?
概念理解:
日常生活中有很多的容器,例如:水桶、茶杯、酒瓶,那么他們都有一個特點,就是裝東西。而 Spring 的容器,就是裝對象的實例的。
3.1 IoC 容器的體系結構
Spring 的容器有兩個:
- BeanFactory
- ApplicationContext
他們兩個都是接口,那么有什么區別呢?見圖如下:
BeanFactory
才是 Spring 容器中的頂層接口。 ApplicationContext
是它的子接口。
簡而言之,BeanFactory
提供了配置框架和基本功能,并在 ApplicationContext
中增加了更多針對企業的功能。
BeanFactory
和 ApplicationContext
的區別: 創建對象的時間點不一樣。
ApplicationContext
:只要一讀取配置文件,默認情況下就會創建對象。
BeanFactory
:什么時候使用,什么時候創建對象。
3.2 IoC 容器實例化的方式
上面已經知道 Spring 的容器是通過一個接口 org.springframework.context.ApplicationContext
表示,并負責實例化,配置和組裝 Bean 對象。容器通過讀取 xml 文件中的配置信息來獲取關于實例化對象,配置屬性等命令。
而 ApplicationContext
只是一個接口,我們通常創建 ClassPathXmlApplicationContext
的實例或者 FileSystemXmlApplicationContext
的實例。前者是從類路徑中獲取上下文定義文件,后者是從文件系統或 URL 中獲取上下文定義文件 。例如:
代碼解釋:
15 行注釋掉的代碼是通過加載類路徑下的配置文件,一般來說 Java 工程放在 src
目錄下。我們使用的是 Maven 工程放在 resources
目錄下。
18 行代碼是通過加載本地 D 盤目錄下的文件來初始化容器, 實例化 bean 對象。
結論
通過上面的兩種方式測試,發現都可以成功初始化容器, 獲取測試的 bean 對象實例。
也證明了容器的初始化可以創建 ClassPathXmlApplicationContext
也可以創建 FileSystemXmlApplicationContext
的實例。
3.3 IoC 容器的使用實例
我們知道了加載配置文件初始化容器的方式,現在了解下容器的使用。其實對于我們而言,已經不陌生了,在第一章第二小節中也已經成功的從容器中獲取了對象實例。
這里我們就回顧一下:
- 容器的初始化必須先配置 xml 文件,代碼回顧如下:
<bean id="user" class="com.wyan.entity.User" ></beans>
- 加載配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- 調用方法
context.getBean("user")
4. 小結
本小節對 IoC 概念做了一個詳解,同時介紹了 IoC 解決的問題,演示了 IoC 的使用實例,對于初學者來說搞清楚概念,理解作用,實踐出結果,就是出色的完成了任務。
技術沒有捷徑可走,多學習可以增加我們的知識,勤練習可以增加我們的經驗,善于總結思路可以提升我們的能力,一起加油。