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