作為Spring新手,邊學(xué)《Spring in Action》邊總結(jié)相關(guān)知識援所。
什么是DI
DI欣除,Dependency Injection历帚,即依賴注入抹缕,不是去依賴“注入”這個東東墨辛,而是將“依賴”這個東東給注入睹簇。
那么什么是依賴太惠?我們都知道,一個稍微大一點的應(yīng)用程序梁只,它都是由若干個對象組成的搪锣,這些對象如果各干各的誰也不理誰构舟,那工作怎么可能完成呢堵幽!所以這些對象肯定都是意識到了一些其它對象的存在朴下,并且要和它們交流通信殴胧,朝著共同的目標(biāo)去努力,這樣才可能達(dá)成目標(biāo)免胃,正所謂眾志成城也羔沙。從編程的角度來說扼雏,這些對象之間存在著依賴關(guān)系。
傳統(tǒng)的建立這些依賴關(guān)系的方法苍蔬,是讓對象自己去記錄碟绑、維護(hù)自己所依賴的對象茎匠,這本是一些本不屬于它們自己工作范圍的事情诵冒。這樣也會增加對象之間的耦合度,使得它們難以復(fù)用侮东、難以測試苗桂。
而在Spring里面煤伟,對象自己不需要負(fù)責(zé)去尋找或是創(chuàng)建它們所依賴的對象便锨,而是由容器(container)來維護(hù)對象之間的引用關(guān)系放案。舉個栗子矫俺,訂單管理模塊可能會需要一個信用卡授權(quán)模塊,但是它不需要去創(chuàng)建這個信用卡授權(quán)模塊——它只需要兩手空空地現(xiàn)身稿湿,自然會有人給它一個信用卡授權(quán)模塊饺藤。
這種創(chuàng)建應(yīng)用程序?qū)ο笾g的依賴關(guān)系的行為涕俗,就是DI的本質(zhì)再姑,也經(jīng)常被稱作裝配(wiring)元镀,被裝配到一起的對象萎坷,稱為bean哆档。有很多裝配的方法瓜浸,首先可以來感受一下配置Spring容器的三種最常見的方法插佛。
配置Spring容器
雖然容器要負(fù)責(zé)創(chuàng)建beans量窘,并且通過DI來協(xié)調(diào)這些對象之間的關(guān)系蚌铜,但當(dāng)然也得靠我們程序員來告訴Spring,要創(chuàng)建哪些beans囚痴,怎樣把它們裝配到一起等等奕谭。Spring提供三種機(jī)制來讓我們做這件事:
- 通過XML顯式配置
- 通過Java顯式配置
- 隱式進(jìn)行bean搜索并自動裝配
上述三種方法該如何選擇呢血柳?書作者Walls的建議是,盡量使用自動配置蹬昌,需要的顯式說明越少就越好混驰。如果必須顯式配置(比如當(dāng)你沒有你要配置的beans的源代碼時),通過Java配置更理想皂贩,因為它類型安全且功能更強(qiáng)栖榨。只有當(dāng)存在方便的XML命名空間可用,而Java配置中又沒有可替代者時明刷,才考慮用XML配置婴栽。
接下來依次學(xué)習(xí)這三種機(jī)制的使用方法。
一辈末、自動裝配beans
Spring從兩個方面來實現(xiàn)自動的裝配:
- 組件掃描(Component scanning)——Spring自動找尋需要在應(yīng)用程序上下文中創(chuàng)建的beans愚争。
- 自動裝配(Autowiring)——Spring自動滿足bean的依賴。
以上兩者組合在一起轰枝,就可以實現(xiàn)強(qiáng)大的自動裝配的功能从隆,將顯式的配置說明控制到最少。具體來說筛武,可以通過@Component硕噩、@ComponentScan辉懒、@Autowired注解來實現(xiàn)自動裝配,下面分別介紹它們的作用。
@Component
被@Componet修飾的類,Spring為會其創(chuàng)建一個bean钞楼。Spring的應(yīng)用程序上下文里宛琅,所有的beans都有一個ID痢艺。當(dāng)@Component不加參數(shù)時哺呜,為該類生成的ID就是其類名(首字母小寫);也可以加參數(shù),如
@Component("someCoolName")
public class SomeClass {}
生成的bean的ID就是someCoolName了环础。
但是組件掃描并不是默認(rèn)開啟的,所以還需要寫一點顯式的配置說明,告訴Spring去找尋被@Component修飾的類,為它們創(chuàng)建beans粱哼。
@ComponentScan
如果存在被@ComponentScan修飾的類绊含,那么Spring就會去掃描找尋組件來生成bean(也可以通過XML文件的方式來配置組件掃描)充甚。
當(dāng)@ComponentScan不加參數(shù)時技矮,掃描范圍就是該類所在的包旁理∈裕可以加字符串參數(shù)霞势,如
@ComponentScan(basePackages = "somepackage")
public class ConfigurationClass {}
這樣掃描范圍就是somepackage包固以;參數(shù)還可以是字符串?dāng)?shù)組,如
@ComponentScan(basePackages = {"somepackage", "anotherpackage"})
public class ConfigurationClass {}
掃描范圍就變成了多個包绪杏。除了字符串蕾久,參數(shù)還可以是類或接口搓逾,如
@ComponentScan(basePackageClasses = {Class1.class, Class2.class})
public class ConfigurationClass {}
這樣掃描范圍就是這些類所在的包端逼。相比于傳字符串形式的參數(shù)顶滩,傳Java類類型的參數(shù)更加類型安全仅醇。另外准谚,雖然這里用的是一個類來作為@ComponentScan的標(biāo)記類奔滑,但更推薦用一個空的接口來做標(biāo)記氓辣,這樣可以更加“重構(gòu)友好”(refactor-friendly)地引用接口弧烤,而不用引用任何實際的程序代碼(它們以后可能會被重構(gòu)到你想要進(jìn)行組件掃描的包之外)话浇。
如果應(yīng)用程序中所有對象都沒有依賴嗅定,那么靠組件掃描就夠了。但很多對象都是依賴其它對象的,因此在裝配beans的時候佛南,也需要把它們具有的所有依賴都一同裝配進(jìn)來蓬蝶。
@Autowired
簡單地說著摔,自動裝配就是讓Spring自動去滿足bean的需求优床,也就是在應(yīng)用程序上下文里去尋找這個bean所需要的其它的beans胆敞。@Autowired注解就是用來告訴Spring着帽,需要進(jìn)行自動裝配(也可以用Java自帶的@Inject注解,兩者存在細(xì)微區(qū)別移层,但基本可以互換)启摄。如下述的CDPlayer類:
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
它的構(gòu)造方法有@Autowired注解,表示當(dāng)Spring創(chuàng)建CDPlayer的bean時幽钢,應(yīng)該用該構(gòu)造方法來實例化歉备,并且傳入一個CompactDisc的bean。
其實不光是構(gòu)造方法匪燕,任何方法都可以用@Autowired注解蕾羊,Spring就會去嘗試滿足該方法的參數(shù)所表達(dá)的依賴。如果沒有bean滿足匹配帽驯,Spring就會在應(yīng)用程序上下文被創(chuàng)建的時候扔一個異常龟再。可以通過設(shè)置@Autowired的required參數(shù)來避免異常:
@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
當(dāng)required為false時尼变,Spring仍然會嘗試去自動裝配利凑,但是如果沒有匹配的bean浆劲,它就不會裝配這個等待被裝配的bean。要小心這樣設(shè)置哀澈,因為未裝配的屬性可以導(dǎo)致空指針異常牌借。
如果存在不只一個bean滿足匹配,Spring就會扔一個表示歧義的異常割按,當(dāng)然有辦法可以管理并避免歧義膨报,這里就不討論了。
二适荣、通過Java裝配beans
盡管大多數(shù)情況下现柠,通過組件掃描和自動裝配是更好的方法,但有些情況下無法使用自動配置弛矛,你將不得不顯式地進(jìn)行配置够吩。比如你想要將第三方庫中的組件裝配進(jìn)程序中,但是沒有它們的源代碼丈氓,也就無法給它們打上@Component這些注解周循,于是自動配置不可行。
通過JavaConfig配置比通過XML配置更為推薦扒寄,前者功能更強(qiáng)鱼鼓,更加類型安全且重構(gòu)友好,因為它就是Java代碼该编。
但同時也要意識到迄本,這些Java代碼和程序中其它的Java代碼又不一樣,因為在概念上课竣,它和程序中的業(yè)務(wù)邏輯嘉赎、領(lǐng)域模型這些是分離的,它屬于配置代碼于樟,因此不應(yīng)該包含任何業(yè)務(wù)邏輯公条,也不應(yīng)該侵入任何包含業(yè)務(wù)邏輯的代碼。實際上迂曲,經(jīng)常把這些配置代碼放置于一個單獨的包靶橱,這樣就不會跟程序的其它邏輯混在一起。
接下來看看具體怎么用JavaConfig進(jìn)行裝配路捧。
@Configuration
要創(chuàng)建一個JavaConfig類关霸,就用@Configuration注解這個類,該注解將它標(biāo)識為一個配置類杰扫,它應(yīng)該包含需要在Spring應(yīng)用程序上下文中創(chuàng)建的beans的詳細(xì)信息队寇。如:
@Configuration
public class CDPlayerConfig {}
它將CDPlayerConfig類標(biāo)記為配置類。
@Bean
如果某方法被@Bean注解章姓,那么Spring就知道該方法將會返回一個對象佳遣,該對象應(yīng)該在Spring應(yīng)用程序上下文中被注冊為一個bean识埋,方法體內(nèi)包含著最終生成bean實例的代碼邏輯。例如下面的代碼聲明了CompactDisc的bean:
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
方法體返回了一個新的SgtPeppers實例(SgtPeppers繼承自CompactDisc)零渐,事實上方法里面可以寫任何Java代碼窒舟,只要最后能返回一個CompactDisc實例。
默認(rèn)情況下相恃,這個bean會被賦予一個與@Bean注解的方法名相同的ID辜纲,上述樣例中就是sgtPeppers笨觅±鼓停可以通過name屬性來賦一個不同的名字:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
CompactDisc的bean比較簡單,它自己沒有依賴的對象〖#現(xiàn)在如果要聲明一個CDPlayer的bean杀糯,它是依賴一個CompactDisc對象的,應(yīng)該怎樣來裝配呢苍苞?
JavaConfig中最簡單的做法就是調(diào)用所需bean的@Bean方法固翰,還是舉例說明,你可以這樣聲明CDPlayer的bean:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
cdPlayer()方法像sgtPeppers()方法一樣羹呵,也有@Bean注解骂际,以此來表示它將會產(chǎn)生一個要在Spring應(yīng)用程序上下文中注冊的bean實例,這個bean的ID是cdPlayer冈欢,與方法名相同歉铝。
cdPlayer()的方法體與sgtPeppers()的有著細(xì)微的不同,前者并沒有通過默認(rèn)構(gòu)造方法來創(chuàng)建實例凑耻,而是調(diào)用了有一個CompactDisc參數(shù)的構(gòu)造方法來創(chuàng)建CDPlayer的實例太示。
看上去CompactDisc的實例是通過調(diào)用sgtPeppers()方法來提供的,但并不是這樣香浩。因為sgtPeppers()方法有@Bean注解类缤,Spring就會攔截任何對它的調(diào)用,并確保該方法提供的bean被返回邻吭,而不是讓它再被調(diào)用一次餐弱。
繼續(xù)舉栗子,假設(shè)你又引進(jìn)了另一個CDPlayer的bean囱晴,和第一個一模一樣:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
如果對sgtPeppers的調(diào)用被當(dāng)成與其它普通Java方法的調(diào)用一樣膏蚓,那么每一個CDPlayer都會被給予一個它自己的SgtPeppers實例。如果我們談?wù)摰氖钦鎸嵉腃D播放機(jī)和壓縮碟片速缆,這倒是有意義的降允,因為當(dāng)你有兩個CD播放機(jī)時,不可能將一張碟片同時插入到兩個播放器中艺糜。
但在軟件中剧董,你可以隨意將同一個SgtPeppers的實例注入到任意多個其它的beans里面去幢尚。默認(rèn)情況下,Spring中的所有beans都是單例翅楼,也沒有什么原因需要你為第二個CDPlayer的bean再創(chuàng)建一個重復(fù)的實例尉剩,所以Spring會阻止對sgtPeppers()的調(diào)用,并確保返回的bean是當(dāng)Spring自己調(diào)用sgtPeppers()時所創(chuàng)建的CompactDisc bean毅臊。因此理茎,兩個CDPlayer的beans都會被給予同一個SgtPeppers的實例。
如果對通過調(diào)用其方法來引用一個bean感到迷惑管嬉,另一種方式也許更容易讓人理解:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
這里皂林,cdPlayer()方法需要一個CompactDisc作為參數(shù),當(dāng)Spring調(diào)用cdPlayer()來創(chuàng)造CDPlayer bean的時候蚯撩,它將一個CompactDisc自動裝配進(jìn)配置方法础倍,然后方法體內(nèi)可以在任何適當(dāng)?shù)臅r候使用該bean。利用這個機(jī)制胎挎,cdPlayer()方法仍然可以將CompactDisc注入CDPlayer的構(gòu)造方法中去沟启,而無需顯式地引用CompactDisc的@Bean方法。
這一引用其它beans的方法通常是最好的選擇犹菇,因為它不依賴在同一個配置類中聲明的CompactDisc bean德迹。事實上,CompactDisc bean也完成可以不用通過JavaConfig來聲明揭芍,它可以被組件掃描所發(fā)現(xiàn)胳搞,也可以在XML中聲明。你還可以將你的配置拆成一個健壯的混合體沼沈,將配置類流酬、XML文件、自動掃描及裝配的beans這三者融合起來列另。不管CompactDisc是怎樣創(chuàng)建的芽腾,Spring都樂于將其交給這個配置方法,用來創(chuàng)建CDPlayer的bean页衙。
任何情況下都有必要意識到摊滔,盡管你是在通過CDPlayer的構(gòu)造方法進(jìn)行DI,在這里你也不是不可以應(yīng)用其它形式的DI店乐。例如艰躺,當(dāng)你想要通過setter方法來注入一個CompactDisc,cdPlayer()也許就是下面這樣了:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
現(xiàn)在又要重復(fù)提醒一下眨八,一個@Bean方法的主體部分可以使用任何需要的Java代碼來生成bean實例腺兴。構(gòu)造方法和setter方法的注入只是其中兩個簡單的例子,你能在一個@Bean注解的方法里做的事情多了去了廉侧,唯一的限制也就只有Java語言本身的能力了页响。
三篓足、通過XML裝配beans
(暫略)