看了Spring in Action(第四版)開頭初嘹,覺得說的很好碴裙。Spring的根本使命是什么,簡化Java開發(fā)陶缺。
為了降低Java開發(fā)的復(fù)雜性钾挟,Spring采取了以下4種關(guān)鍵策略:
- 基于POJO的輕量級和最小侵入性編程;
- 通過依賴注入和面向接口實現(xiàn)松耦合饱岸;
- 基于切面和慣例進行聲明式編程掺出;
- 通過切面和模板減少樣板式代碼。
激發(fā)POJO的潛能
很多框架的使用都會“強迫”我們的應(yīng)用繼承他們的類或者實現(xiàn)他們的接口苫费,這種侵入式編程會導(dǎo)致應(yīng)用于框架綁死汤锨。Spring不會強迫你實現(xiàn)Spring規(guī)范的接口或繼承Spring規(guī)范的類,相反百框,在基于Spring構(gòu)建的應(yīng)用中闲礼,它的類通常沒有任何痕跡表明你使用了Spring。最壞的場景是铐维,一個類或許會使用Spring注解位仁,但它依舊是POJO。
`package com.habuma.spring
public class HelloWorldBean{
public String sayHello(){
return "Hello World";
}
}`
可以看到方椎,這是一個簡單普通的Java類——POJO。沒有任何地方表明它是一個Spring組件钧嘶。Spring的非侵入編程模型意味著這個類在Spring應(yīng)用和非Spring應(yīng)用中都可以發(fā)揮同樣的作用棠众。
依賴注入
依賴注入是個聽上去個很高端的詞,很多人都不懂是什么意思有决,我覺得Spring in Action就解釋的很清楚闸拿。
我們?yōu)槭裁葱枰蕾囎⑷肽兀?/p>
任何一個有實際意義的應(yīng)用(肯定比Hello World示例更復(fù)雜)都會由兩個或者更多的類組成,這些類相互之間進行協(xié)作來完成特定的業(yè)務(wù)邏輯书幕。按照傳統(tǒng)的做法新荤,每個對象負責(zé)管理與自己相互協(xié)作的對象(即它所依賴的對象)的引用,這將會導(dǎo)致高度耦合和難以測試的代碼台汇。
package sia.knights;
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest() {
quest.embark();
}
}
可以看到苛骨,DamselRescuingKnight在它的構(gòu)造函數(shù)中自行創(chuàng)建了RescueDamselQuest篱瞎。這使得DamselRescuingKnight緊密地和RescueDamselQuest耦合到了一起,因此極大地限制了這個騎士執(zhí)行探險的能力痒芝。如果一個少女需要救援俐筋,這個騎士能夠召之即來。但是如果一條惡龍需要殺掉严衬,或者一個圓桌……額……需要滾起來澄者,那么這個騎士就愛莫能助了。
更糟糕的是请琳,為這個DamselRescuingKnight編寫單元測試將出奇地困難粱挡。在這樣的一個測試中,你必須保證當(dāng)騎士的embarkOnQuest()方法被調(diào)用的時候俄精,探險的embark()方法也要被調(diào)用询筏。但是沒有一個簡單明了的方式能夠?qū)崿F(xiàn)這一點。很遺憾嘀倒,DamselRescuingKnight將無法進行測試屈留。
耦合具有兩面性(two-headed beast)。一方面测蘑,緊密耦合的代碼難以測試灌危、難以復(fù)用、難以理解碳胳,并且典型地表現(xiàn)出“打地鼠”式的bug特性(修復(fù)一個bug勇蝙,將會出現(xiàn)一個或者更多新的bug)。另一方面挨约,一定程度的耦合又是必須的——完全沒有耦合的代碼什么也做不了味混。為了完成有實際意義的功能,不同的類必須以適當(dāng)?shù)姆绞竭M行交互诫惭∥涛總而言之,耦合是必須的夕土,但應(yīng)當(dāng)被小心謹慎地管理馆衔。
通過DI,對象的依賴關(guān)系將由系統(tǒng)中負責(zé)協(xié)調(diào)各對象的第三方組件在創(chuàng)建對象的時候進行設(shè)定怨绣。對象無需自行創(chuàng)建或管理它們的依賴關(guān)系角溃,依賴關(guān)系將被自動注入到需要它們的對象當(dāng)中去。
package sia.knights;
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
quest.embark();
}
}
我們可以看到篮撑,不同于之前的DamselRescuingKnight减细,BraveKnight沒有自行創(chuàng)建探險任務(wù),而是在構(gòu)造的時候把探險任務(wù)作為構(gòu)造器參數(shù)傳入赢笨。這是依賴注入的方式之一未蝌,即構(gòu)造器注入(constructor injection)驮吱。
更重要的是,傳入的探險類型是Quest树埠,也就是所有探險任務(wù)都必須實現(xiàn)的一個接口糠馆。所以,BraveKnight能夠響應(yīng)RescueDamselQuest怎憋、 SlayDragonQuest又碌、 MakeRoundTableRounderQuest等任意的Quest實現(xiàn)。
這里的要點是BraveKnight沒有與任何特定的Quest實現(xiàn)發(fā)生耦合绊袋。對它來說毕匀,被要求挑戰(zhàn)的探險任務(wù)只要實現(xiàn)了Quest接口,那么具體是哪種類型的探險就無關(guān)緊要了癌别。這就是DI所帶來的最大收益——松耦合皂岔。如果一個對象只通過接口(而不是具體實現(xiàn)或初始化過程)來表明依賴關(guān)系,那么這種依賴就能夠在對象本身毫不知情的情況下展姐,用不同的具體實現(xiàn)進行替換躁垛。
至入如何注入,我們后續(xù)再討論圾笨。
應(yīng)用切面
DI能夠讓相互協(xié)作的軟件組件保持松散耦合教馆,而面向切面編程(aspect-orientedprogramming,AOP)允許你把遍布應(yīng)用各處的功能分離出來形成可重用的組件擂达。
面向切面編程往往被定義為促使軟件系統(tǒng)實現(xiàn)關(guān)注點的分離一項技術(shù)土铺。系統(tǒng)由許多不同的組件組成,每一個組件各負責(zé)一塊特定功能板鬓。除了實現(xiàn)自身核心的功能之外悲敷,這些組件還經(jīng)常承擔(dān)著額外的職責(zé)。諸如日志俭令、事務(wù)管理和安全這樣的系統(tǒng)服務(wù)經(jīng)常融入到自身具有核心業(yè)務(wù)邏輯的組件中去后德,這些系統(tǒng)服務(wù)通常被稱為橫切關(guān)注點,因為它們會跨越系統(tǒng)的多個組件抄腔。
如果將這些關(guān)注點分散到多個組件中去探遵,你的代碼將會帶來雙重的復(fù)雜性:
- 實現(xiàn)系統(tǒng)關(guān)注點功能的代碼將會重復(fù)出現(xiàn)在多個組件中。這意味著如果你要改變這些關(guān)注點的邏輯妓柜,必須修改各個模塊中的相關(guān)實現(xiàn)。即使你把這些關(guān)注點抽象為一個獨立的模塊涯穷,其他模塊只是調(diào)用它的方法棍掐,但方法的調(diào)用還是會重復(fù)出現(xiàn)在各個模塊中。
- 組件會因為那些與自身核心業(yè)務(wù)無關(guān)的代碼而變得混亂拷况。一個向地址簿增加地址條目的方法應(yīng)該只關(guān)注如何添加地址作煌,而不應(yīng)該關(guān)注它是不是安全的或者是否需要支持事務(wù)掘殴。
圖1.2展示了這種復(fù)雜性。左邊的業(yè)務(wù)對象與系統(tǒng)級服務(wù)結(jié)合得過于緊密粟誓。每個對象不但要知道它需要記日志奏寨、進行安全控制和參與事務(wù)望浩,還要親自執(zhí)行這些服務(wù)祸泪。
AOP能夠使這些服務(wù)模塊化拙寡,并以聲明的方式將它們應(yīng)用到它們需要影響的組件中去拗踢。所造成的結(jié)果就是這些組件會具有更高的內(nèi)聚性并且會更加關(guān)注自身的業(yè)務(wù)尔觉,完全不需要了解涉及系統(tǒng)服務(wù)所帶來復(fù)雜性环戈∩枰祝總之逗柴,AOP能夠確保POJO的簡單性。
如圖1.3所示顿肺,我們可以把切面想象為覆蓋在很多組件之上的一個外殼戏溺。應(yīng)用是由那些實現(xiàn)各自業(yè)務(wù)功能的模塊組成的。借助AOP屠尊,可以使用各種功能層去包裹核心業(yè)務(wù)層旷祸。這些層以聲明的方式靈活地應(yīng)用到系統(tǒng)中,你的核心應(yīng)用甚至根本不知道它們的存在知染。這是一個非常強大的理念肋僧,可以將安全、事務(wù)和日志關(guān)注點與核心業(yè)務(wù)邏輯相分離控淡。
至入如何應(yīng)用切面,我們后續(xù)再討論掺炭。
還有使用模板消除樣板式代碼就不討論了辫诅,比如JDBC Templete。