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