Spring框架對Java開發(fā)的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反轉(zhuǎn))和AOP,平時(shí)使用最多的就是其中的IOC,我們通過將組件交由Spring的IOC容器管理,將對象的依賴關(guān)系由Spring控制,避免硬編碼所造成的過度程序耦合。前幾天的時(shí)候,筆者的同事問我為什么要使用構(gòu)造器的注入方式,我回答說因?yàn)镾pring文檔推薦這種,而說不出為什么 T^T,后面抽時(shí)間了解了一下,下面就是筆者要討論的就是其注入方式。
二、常見的三種注入方式
筆者為了方便起見就只是用注解的方式注入(現(xiàn)在也很少使用xml了吧,(~ ̄▽ ̄)~)
2.1 field注入
@Controller
public class FooController {
@Autowired
//@Inject
private FooService fooService;
//簡單的使用例子,下同
public List<Foo> listFoo() {
return fooService.list();
}
}
這種注入方式應(yīng)該是筆者目前為止開發(fā)中見到的最常見的注入方式。原因很簡單:
- 注入方式非常簡單:加入要注入的字段,附上
@Autowired,即可完成。 - 使得整體代碼簡潔明了,看起來美觀大方。
2.2 構(gòu)造器注入
@Controller
public class FooController {
private final FooService fooService;
@Autowired
public FooController(FooService fooService) {
this.fooService = fooService;
}
//使用方式上同,略
}
在Spring4.x版本中推薦的注入方式就是這種,相較于上面的field注入方式而言,就顯得有點(diǎn)難看,特別是當(dāng)注入的依賴很多(5個(gè)以上)的時(shí)候,就會(huì)明顯的發(fā)現(xiàn)代碼顯得很臃腫。對于從field注入轉(zhuǎn)過來+有強(qiáng)迫癥的園友 來說,簡直可以說是石樂志 (`Д′*)9。對于這一點(diǎn)我們后面再來討論,別急。
2.3 setter注入
@Controller
public class FooController {
private FooService fooService;
//使用方式上同,略
@Autowired
public void setFooService(FooService fooService) {
this.fooService = fooService;
}
}
在Spring3.x剛推出的時(shí)候,推薦使用注入的就是這種,筆者現(xiàn)在也基本沒看到過這種注解方式,寫起來麻煩,當(dāng)初推薦Spring自然也有他的道理,這里我們引用一下Spring當(dāng)時(shí)的原話:
The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.
Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.
咳咳,簡單的翻譯一下就是:構(gòu)造器注入?yún)?shù)太多了,顯得很笨重,另外setter的方式能用讓類在之后重新配置或者重新注入。
那么后面為什么又換成構(gòu)造器注入了呢?(喂喂喂,Spring你前一大版本還貶低構(gòu)造器注入,后面就立刻捧人家了不好吧,不過能用于承認(rèn)自己的錯(cuò)誤,才是真正令人稱贊的地方吧 (??????)??)
三、構(gòu)造器注入的好處
先來看看Spring在文檔里怎么說:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not
null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
咳咳,再來簡單的翻譯一下:這個(gè)構(gòu)造器注入的方式啊,能夠保證注入的組件不可變,并且確保需要的依賴不為空。此外,構(gòu)造器注入的依賴總是能夠在返回客戶端(組件)代碼的時(shí)候保證完全初始化的狀態(tài)。
下面來簡單的解釋一下:
- 依賴不可變:其實(shí)說的就是final關(guān)鍵字,這里不再多解釋了。不明白的園友可以回去看看Java語法。
- 依賴不為空(省去了我們對其檢查):當(dāng)要實(shí)例化FooController的時(shí)候,由于自己實(shí)現(xiàn)了有參數(shù)的構(gòu)造函數(shù),所以不會(huì)調(diào)用默認(rèn)構(gòu)造函數(shù),那么就需要Spring容器傳入所需要的參數(shù),所以就兩種情況:1、有該類型的參數(shù)->傳入,OK 。2:無該類型的參數(shù)->報(bào)錯(cuò)。所以保證不會(huì)為空,Spring總不至于傳一個(gè)null進(jìn)去吧 ??
- 完全初始化的狀態(tài):這個(gè)可以跟上面的依賴不為空結(jié)合起來,向構(gòu)造器傳參之前,要確保注入的內(nèi)容不為空,那么肯定要調(diào)用依賴組件的構(gòu)造方法完成實(shí)例化。而在Java類加載實(shí)例化的過程中,構(gòu)造方法是最后一步(之前如果有父類先初始化父類,然后自己的成員變量,最后才是構(gòu)造方法,這里不詳細(xì)展開。)。所以返回來的都是初始化之后的狀態(tài)。
等等,比較完了setter注入與構(gòu)造器注入的優(yōu)缺點(diǎn),你還沒用說使用field注入與構(gòu)造器的比較呢!那么我們再回頭看一看使用最多的field注入方式:
//承接上面field注入的代碼,假如客戶端代碼使用下面的調(diào)用(或者再Junit測試中使用)
//這里只是模擬一下,正常來說我們只會(huì)暴露接口給客戶端,不會(huì)暴露實(shí)現(xiàn)。
FooController fooController = new FooController();
fooController.listFoo(); // -> NullPointerException
如果使用field注入,缺點(diǎn)顯而易見,對于IOC容器以外的環(huán)境,除了使用反射來提供它需要的依賴之外,無法復(fù)用該實(shí)現(xiàn)類。而且將一直是個(gè)潛在的隱患,因?yàn)槟悴徽{(diào)用將一直無法發(fā)現(xiàn)NPE的存在。
還值得一提另外一點(diǎn)是:使用field注入可能會(huì)導(dǎo)致循環(huán)依賴,即A里面注入B,B里面又注入A:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
如果使用構(gòu)造器注入,在spring項(xiàng)目啟動(dòng)的時(shí)候,就會(huì)拋出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?從而提醒你避免循環(huán)依賴,如果是field注入的話,啟動(dòng)的時(shí)候不會(huì)報(bào)錯(cuò),在使用那個(gè)bean的時(shí)候才會(huì)報(bào)錯(cuò)。
四、答疑
好了,相信已經(jīng)園友們知道了構(gòu)造器注入的好處,那么回到了在前面提到的問題:
Q1:跟3.x里說的一樣,我要是有大量的依賴要注入,構(gòu)造方法不會(huì)顯得很臃腫嗎?
對于這個(gè)問題,說明你的類當(dāng)中有太多的責(zé)任,那么你要好好想一想是不是自己違反了類的單一性職責(zé)原則,從而導(dǎo)致有這么多的依賴要注入。
Q2:是不是其他的注入方式都不適合用了呢?
當(dāng)然不是,存在即是合理!setter的方式既然一開始被Spring推薦肯定是有它的道理,像之前提到的setter的方式能用讓類在之后重新配置或者重新注入,就是其優(yōu)點(diǎn)之一。除此之外,如果一個(gè)依賴有多種實(shí)現(xiàn)方式,我們可以使用@Qualifier,在構(gòu)造方法里選擇對應(yīng)的名字注入,也可以使用field或者setter的方式來手動(dòng)配置要注入的實(shí)現(xiàn)。
五、總結(jié)
使用構(gòu)造器注入的好處:
- 保證依賴不可變(final關(guān)鍵字)
- 保證依賴不為空(省去了我們對其檢查)
- 保證返回客戶端(調(diào)用)的代碼的時(shí)候是完全初始化的狀態(tài)
- 避免了循環(huán)依賴
- 提升了代碼的可復(fù)用性
另外,當(dāng)有一個(gè)依賴有多個(gè)實(shí)現(xiàn)的使用,推薦使用field注入或者setter注入的方式來指定注入的類型。這是spring官方博客對setter注入方式和構(gòu)造器注入的比較。謝謝各位園友觀看,如果有描述不對的地方歡迎指正,與大家共同進(jìn)步!