一、概述
- Spring是一個(gè)輕量級(jí)的開源JavaEE框架
- Spring可以解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性
- Spring兩大核心部分:IoC和AOP
特點(diǎn): - 方便解耦,簡(jiǎn)化開發(fā)
- AOP編程支持
- 方便程序測(cè)試
- 方便和其他框架整合
- 方便事務(wù)操作
- 降低API開發(fā)難度
IoC和AOP是Spring的核心,分析其原理:
二、IoC解耦推導(dǎo)
- 我們都知道,IoC是控制反轉(zhuǎn),通俗講就是把對(duì)象創(chuàng)建和對(duì)象之間的調(diào)用交給Spring管理,通過(guò)簡(jiǎn)單的xml配置就可以創(chuàng)建和調(diào)用對(duì)象,其主要目的就是解耦,降低代碼之間的耦合度,咱們就從傳統(tǒng)方式到IoC來(lái)一步一步講述怎樣把耦合度降到最低。
- 所謂解耦就是降低類之間的依賴和方法之間的依賴
1. 傳統(tǒng)直接調(diào)用對(duì)象
- 在我們傳統(tǒng)的開發(fā)方式中,是直接采取new對(duì)象的方式創(chuàng)建對(duì)象,經(jīng)??梢钥吹絪ervice調(diào)用dao這樣的代碼,如果是直接調(diào)用,我們來(lái)看看是怎么樣子的,創(chuàng)建UserService和UserDao ,通過(guò)UserService調(diào)用UserDao:
// UserDao1類
public class UserDao1 {
public void say(){
System.out.println("I am userDao1");
}
}
// UserService類,調(diào)用UserDao1類中的方法
public class UserService {
public void getUser(UserDao1 userDao1){
userDao1.say();
}
}
在上面的代碼中,是最傳統(tǒng)的調(diào)用方式,通過(guò)service調(diào)用dao,可以得到我們想要的結(jié)果,打印出:“I am userDao1”,但是,突然,產(chǎn)品經(jīng)理想要UserDao2一個(gè)新的類也可以在UserService中進(jìn)行調(diào)用,這個(gè)時(shí)候,就需要將代碼改為如下:
// UserDao1類
public class UserDao1 {
public void say(){
System.out.println("I am userDao1");
}
}
// UserDao2類
public class UserDao2 {
public void say(){
System.out.println("I am userDao2");
}
}
// UserService類,調(diào)用UserDao1和UserDao2類中的方法
public class UserService {
public void getUser(UserDao1 userDao1){
userDao1.say();
}
public void getUser(UserDao2 userDao2){
userDao2.say();
}
}
- 可以看到,我們不僅要新建一個(gè)UserDao2類,還需要修改UserService中的代碼,萬(wàn)一,突然,產(chǎn)品經(jīng)理想把UserDao3、UserDao4、UserDao5.....這樣具有相同功能的類也在userService中作為參數(shù)進(jìn)行調(diào)用,新建這些類倒還好,避免不了,問(wèn)題是還要修改UserService類,簡(jiǎn)直頭大....
- 其實(shí),上面的代碼中,UserService就和要調(diào)用的dao類具有一種很強(qiáng)的聯(lián)系,我們把這種聯(lián)系稱為強(qiáng)耦合關(guān)系,這種強(qiáng)耦合關(guān)系是不利于開發(fā)的,因此我們需要解耦,首先想到的便是使用接口進(jìn)行解耦,也就是面向接口編程。
2. 接口解耦
將上面的代碼進(jìn)行修改,將UserDao定義為接口,然后去實(shí)現(xiàn)這個(gè)接口,再進(jìn)行調(diào)用,如下:// UserDao接口
public interface UserDao {
void say();
}
// 接口實(shí)現(xiàn)類UserDao1
public class UserDaoImpl1 implements UserDao {
@Override
public void say() {
System.out.println("I am userDao1");
}
}
// 接口實(shí)現(xiàn)類UserDao2
public class UserDaoImpl2 implements UserDao {
@Override
public void say() {
System.out.println("I am userDao2");
}
}
// UserService中進(jìn)行調(diào)用
public class UserService {
public void getUser(UserDao userDao){
userDao.say();
}
}
在上面的代碼中,我們可以看到,UserService類中g(shù)etUser方法參數(shù)可以是UserDao1類型的,也可以是UserDao2類型的,不像之前的代碼,只能是指定的UserDao。這時(shí),UserService和UserDao1、UserDao2聯(lián)系的就沒(méi)那么緊密了,這是一種弱耦合關(guān)系,通過(guò)接口來(lái)進(jìn)行解耦。
但是仔細(xì)查看上面的代碼,你會(huì)發(fā)現(xiàn),接口和實(shí)現(xiàn)類之間還是存在強(qiáng)耦合關(guān)系,在面向接口編程中,我們常常會(huì)看到類似這樣的代碼:
UserDao userDao = new UserDaoImpl1();
假設(shè)現(xiàn)在不用這個(gè)UserDaoImpl1了,而改用UserDao的另一個(gè)實(shí)現(xiàn)類UserDaoImpl2,代碼就要改為如下:
UserDao userDao = new UserDaoImpl2();
- 這樣也就是接口和實(shí)現(xiàn)類出現(xiàn)了耦合,為了進(jìn)一步解耦,我們就使用下面的工廠模式。
3. 工廠模式解耦
- 工廠的意思也就是一個(gè)批量制造同樣規(guī)格(規(guī)格也就是接口類所提供好的規(guī)范)類的類,所謂的工廠模式也就是將所有的創(chuàng)建對(duì)象任務(wù)交給了一個(gè)“中間人”,也就是工廠類來(lái)實(shí)現(xiàn),要想使用對(duì)象,直接找工廠類,實(shí)現(xiàn)類必須要從工廠中取出來(lái)。
而要使用工廠模式進(jìn)行解耦,我們需要先將創(chuàng)建對(duì)象交給工廠類:
// 工廠類
public class BeanFactory {
// 創(chuàng)建并返回UserDaoImpl1
public static UserDao getUserDao1(){
return new UserDaoImpl1();
}
// 創(chuàng)建并返回UserDaoImpl2
public static UserDao getUserDao2(){
return new UserDaoImpl2();
}
}
將創(chuàng)建對(duì)象交給工廠類,調(diào)用關(guān)系就轉(zhuǎn)變?yōu)槿缦拢?/p>
UserDao userDao = new UserDaoImpl1(); ===> UserDao userDao1 = BeanFactory.getUserDao1();
UserDao userDao = new UserDaoImpl2(); ===> UserDao userDao2 = BeanFactory.getUserDao2();
- 這樣一來(lái),我們創(chuàng)建對(duì)象只需要調(diào)用工廠類BeanFactory中的方法即可,調(diào)用時(shí)不是直接通過(guò)接口,而是通過(guò)工廠類,將創(chuàng)建對(duì)象交給了工廠類,就降低了接口和實(shí)現(xiàn)類之間的耦合。
- 上面的方法雖然降低了接口和實(shí)現(xiàn)類之間的耦合度,但是,這樣接口和工廠類之間就產(chǎn)生了耦合,為了再次解耦,我們引入了反射+xml配置文件的方式進(jìn)行再次解耦。
4. xml 配置 + 反射 + 工廠解耦(IoC底層的實(shí)現(xiàn))
使用xml配置文件
<bean id="userDao" class="**.UserDaoImpl">
工廠類
class BeanFactory {
public static UserDao getUserDao(String id) {
// String className = 解析配置文件xml 拿到id對(duì)應(yīng)的class
// 反射
class clazz = class.forName(className);
return clazz.newInstance();
}
}
可以看到,在這個(gè)工廠類中,并沒(méi)有直接像上面那樣直接new對(duì)象,而是使用了xml解析和反射方式創(chuàng)建對(duì)象,分析如下:
- 通過(guò)xml解析獲取對(duì)象中屬性的值
- 通過(guò)反射得到字節(jié)碼文件
- 通過(guò)字節(jié)碼文件創(chuàng)建對(duì)象
這樣的話如果我們需要改UserDao的實(shí)現(xiàn)類的類型,我們可以直接在配置文件中修改,就不需要修改代碼,這就是IoC的解耦。
三、IoC 原理理解
1. IoC是什么
IoC:Inversion of Control(控制反轉(zhuǎn)),這不是什么技術(shù),而是一種設(shè)計(jì)思想,在java開發(fā)中,IoC意味著將你設(shè)計(jì)好的對(duì)象交給容器,而不是傳統(tǒng)的在你的對(duì)象內(nèi)部直接控制,如何理解好Ioc呢?理解好IoC的關(guān)鍵是要明確“誰(shuí)控制誰(shuí),控制什么,為何是反轉(zhuǎn)(有反轉(zhuǎn)就應(yīng)該有正轉(zhuǎn)了),哪些方面反轉(zhuǎn)了”,那我們來(lái)深入分析一下:
誰(shuí)控制誰(shuí),控制什么:傳統(tǒng)Java SE程序設(shè)計(jì),我們直接在對(duì)象內(nèi)部通過(guò)new進(jìn)行創(chuàng)建對(duì)象,是程序主動(dòng)去創(chuàng)建依賴對(duì)象;而IoC是有專門一個(gè)容器來(lái)創(chuàng)建這些對(duì)象,即由IoC容器來(lái)控制對(duì) 象的創(chuàng)建;誰(shuí)控制誰(shuí)?當(dāng)然是IoC 容器控制了對(duì)象;控制什么?那就是主要控制了外部資源獲?。ú恢皇菍?duì)象包括比如文件等)
為何是反轉(zhuǎn),哪些方面反轉(zhuǎn)了:有反轉(zhuǎn)就有正轉(zhuǎn),傳統(tǒng)應(yīng)用程序是由我們自己在對(duì)象中主動(dòng)控制去直接獲取依賴對(duì)象,也就是正轉(zhuǎn);而反轉(zhuǎn)則是由容器來(lái)幫忙創(chuàng)建及注入依賴對(duì)象;為何是反轉(zhuǎn)?因?yàn)橛扇萜鲙臀覀儾檎壹白⑷胍蕾噷?duì)象,對(duì)象只是被動(dòng)的接受依賴對(duì)象,所以是反轉(zhuǎn);哪些方面反轉(zhuǎn)了?依賴對(duì)象的獲取被反轉(zhuǎn)了。
2. IoC能做什么
- IoC 不是一種技術(shù),只是一種思想,一個(gè)重要的面向?qū)ο缶幊痰姆▌t,它能指導(dǎo)我們?nèi)绾卧O(shè)計(jì)出松耦合、更優(yōu)良的程序。傳統(tǒng)應(yīng)用程序都是由我們?cè)陬悆?nèi)部主動(dòng)創(chuàng)建依賴對(duì)象,從而導(dǎo)致類與類之間高耦合,難于測(cè)試;有了IoC容器后,把創(chuàng)建和查找依賴對(duì)象的控制權(quán)交給了容器,由容器進(jìn)行注入組合對(duì)象,所以對(duì)象與對(duì)象之間是 松散耦合,這樣也方便測(cè)試,利于功能復(fù)用,更重要的是使得程序的整個(gè)體系結(jié)構(gòu)變得非常靈活。
其實(shí)IoC對(duì)編程帶來(lái)的最大改變不是從代碼上,而是從思想上,發(fā)生了“主從換位”的變化。應(yīng)用程序原本是老大,要獲取什么資源都是主動(dòng)出擊,但是在IoC/DI思想中,應(yīng)用程序就變成被動(dòng)的了,被動(dòng)的等待IoC容器來(lái)創(chuàng)建并注入它所需要的資源了。
IoC很好的體現(xiàn)了面向?qū)ο笤O(shè)計(jì)法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對(duì)象找相應(yīng)的依賴對(duì)象并注入,而不是由對(duì)象主動(dòng)去找。
3. IoC和DI - DI:DI—Dependency Injection,即“依賴注入”:組件之間依賴關(guān)系由容器在運(yùn)行期決定,形象的說(shuō),即由容器動(dòng)態(tài)的將某個(gè)依賴關(guān)系注入到組件之中。依賴注入的目的并非為軟件系統(tǒng)帶來(lái)更多功能,而是為了提升組件重用的頻率,并為系統(tǒng)搭建一個(gè)靈活、可擴(kuò)展的平臺(tái)。通過(guò)依賴注入機(jī)制,我們只需要通過(guò)簡(jiǎn)單的配置,而無(wú)需任何代碼就可指定目標(biāo)需要的資源,完成自身的業(yè)務(wù)邏輯,而不需要關(guān)心具體的資源來(lái)自何處,由誰(shuí)實(shí)現(xiàn)。
理解DI的關(guān)鍵是:“誰(shuí)依賴誰(shuí),為什么需要依賴,誰(shuí)注入誰(shuí),注入了什么”,那我們來(lái)深入分析一下:
誰(shuí)依賴于誰(shuí):當(dāng)然是應(yīng)用程序依賴于IoC容器;
為什么需要依賴:應(yīng)用程序需要IoC容器來(lái)提供對(duì)象需要的外部資源;
誰(shuí)注入誰(shuí):很明顯是IoC容器注入應(yīng)用程序某個(gè)對(duì)象,應(yīng)用程序依賴的對(duì)象;
注入了什么:就是注入某個(gè)對(duì)象所需要的外部資源(包括對(duì)象、資源、常量數(shù)據(jù))。
IoC和DI由什么關(guān)系呢?其實(shí)它們是同一個(gè)概念的不同角度描述,由于控制反轉(zhuǎn)概念比較含糊(可能只是理解為容器控制對(duì)象這一個(gè)層面,很難讓人想到誰(shuí)來(lái)維護(hù)對(duì)象關(guān)系),所以2004年大師級(jí)人物Martin Fowler又給出了一個(gè)新的名字:“依賴注入”,相對(duì)IoC 而言,“依賴注入”明確描述了“被注入對(duì)象依賴IoC容器配置依賴對(duì)象”。
總之依賴注入的意思是你需要的東西不是由你創(chuàng)建的,而是第三方,或者說(shuō)容器提供給你的。這樣的設(shè)計(jì)符合正交性,即所謂的松耦合。