使用Spring開(kāi)發(fā)時(shí)募胃,我們通常有兩種依賴(lài)注入的方式,基于注解@Autowired的依賴(lài)注入和基于構(gòu)造函數(shù)的依賴(lài)注入畦浓。
用IDEA開(kāi)發(fā)過(guò)程中痹束,如果使用@Autowired注入,通常會(huì)有如下警告
Inspection info: Spring Team recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".
翻譯成中文:
spring 團(tuán)隊(duì)建議:“在bean中始終使用基于構(gòu)造函數(shù)的依賴(lài)注入讶请,始終使用斷言來(lái)強(qiáng)制依賴(lài)”祷嘶。
為什么spring團(tuán)隊(duì)會(huì)這樣建議呢?
可能會(huì)出現(xiàn)空指針異常
首先我們看一個(gè)使用@Autowired注入的簡(jiǎn)單例子:
public class Car {
@Autowired
private Wheel wheel;
public void run() {
wheel.roll();
}
}
假設(shè)測(cè)試代碼如下夺溢,就會(huì)發(fā)生空指針異常:
Car car = new Car();
car.run();// -> NullPointerException
出現(xiàn)這種問(wèn)題的關(guān)鍵在于论巍,Car允許創(chuàng)建無(wú)狀態(tài)的對(duì)象,也就是說(shuō)在構(gòu)建Car時(shí)允許Wheel為空风响。
下面我們使用構(gòu)造注入的方式:
public class Car {
private final Wheel wheel;
public Car(Wheel wheel) {
Assert.notNull(wheel,"Wheel must not be null");
this.wheel = wheel;
}
public void run() {
wheel.roll();
}
}
這樣我們就有如下優(yōu)點(diǎn):
- 在創(chuàng)建Car對(duì)象的時(shí)候嘉汰,強(qiáng)制依賴(lài)Wheel對(duì)象,確保創(chuàng)建Car對(duì)象時(shí)每個(gè)對(duì)象都是有效狀態(tài)状勤。
- 構(gòu)造器中可以添加對(duì)象初始化的校驗(yàn)邏輯
- 可以清楚的區(qū)分對(duì)象是通過(guò)setter方法注入的(非final對(duì)象)還是通過(guò)強(qiáng)制依賴(lài)注入的(final對(duì)象)
構(gòu)造注入代碼變得臃腫鞋怀?
或許有的讀者可能會(huì)說(shuō),構(gòu)造注入的話(huà)持搜,如果依賴(lài)的對(duì)象很多密似,構(gòu)造器參數(shù)就會(huì)很多,顯得代碼很臃腫葫盼。這種情況的話(huà)残腌,就要考慮這個(gè)類(lèi)是符合足單一職責(zé)原則了,將這個(gè)類(lèi)拆分為多個(gè)類(lèi)贫导。
而且使用@Autowired的自動(dòng)裝配會(huì)讓依賴(lài)對(duì)象變得很容易抛猫,隨著項(xiàng)目的迭代,自動(dòng)注入的對(duì)象可能會(huì)變得很多脱盲,但是使用構(gòu)造注入邑滨,構(gòu)造器就會(huì)變得很臃腫,提醒你代碼里有bad smell了钱反,需要拆分或重構(gòu)代碼了掖看。
還有一個(gè)問(wèn)題是@Autowired注入的對(duì)象無(wú)法使用final關(guān)鍵字匣距,因?yàn)閒inal對(duì)象必須在構(gòu)造器中初始化。
@Autowired測(cè)試不友好
使用注解的自動(dòng)裝配哎壳,我們的業(yè)務(wù)代碼確實(shí)會(huì)變得比較少毅待,但是單元測(cè)試該如何寫(xiě)呢?
Wheel wheel = Mock(Wheel);
Car car = new Car();
//通過(guò)反射來(lái)將wheel對(duì)象注入到Car對(duì)象里
car.run();
通過(guò)反射注入到Car對(duì)象里归榕,我們的單元測(cè)試代碼就會(huì)顯得很繁瑣尸红,或者在Car對(duì)象里提供一個(gè)Wheel的setter方法?這樣代碼不是很優(yōu)雅刹泄。
如果是構(gòu)造注入外里,單元測(cè)試就會(huì)變成如下:
Wheel wheel = Mock(Wheel);
Car car = new Car(wheel);
car.run();
單元測(cè)試代碼就會(huì)變得很優(yōu)雅,而且在后續(xù)的開(kāi)發(fā)中特石,如果Car對(duì)象添加了強(qiáng)制依賴(lài)的Tank對(duì)象盅蝗,單元測(cè)試也不會(huì)出現(xiàn)沒(méi)有設(shè)置的強(qiáng)制依賴(lài)項(xiàng)。
Spring 的DI設(shè)計(jì)模式姆蘸,是將依賴(lài)關(guān)系的創(chuàng)建和類(lèi)本身分離墩莫,將依賴(lài)關(guān)系創(chuàng)建的職責(zé)交給了類(lèi)注入器做,允許程序設(shè)計(jì)的松耦合逞敷,并遵循單一職責(zé)原則和依賴(lài)反轉(zhuǎn)原則狂秦。因此使用@Autowired自動(dòng)裝配的字段在Spring容器之外無(wú)法使用(不包含通過(guò)反射設(shè)置對(duì)象的方式)。
構(gòu)造注入可以在受影響的類(lèi)中輕松表明對(duì)象的依賴(lài)關(guān)系推捐,但是@Autowired的自動(dòng)裝配其實(shí)對(duì)外隱藏了這些依賴(lài)關(guān)系裂问,需要到對(duì)應(yīng)的類(lèi)中查看代碼才能明確依賴(lài)。
使用Lombok來(lái)解決構(gòu)造注入樣板代碼的問(wèn)題
Lombok是一個(gè)強(qiáng)大的java樣板代碼解決方案玖姑,這里來(lái)介紹下使用Lombok簡(jiǎn)化構(gòu)造注入的代碼:
@RequiredArgsConstructor
public class Car {
private final @NonNull Wheel wheel;
public void run() {
wheel.roll();
}
}
@RequiredArgsConstructor
注解會(huì)在編譯過(guò)程中愕秫,將所有的final對(duì)象作為參數(shù)添加到構(gòu)造器中。
小結(jié)
下面我們來(lái)總結(jié)下注解注入和構(gòu)造注入的優(yōu)缺點(diǎn):
注解注入
++ 寫(xiě)更少的代碼
-- 代碼變得不安全
-- 單元測(cè)試會(huì)比較復(fù)雜
-- 無(wú)法使用fianl對(duì)象
-- 違反單一職責(zé)原則變得很容易
-- 對(duì)受影響的類(lèi)隱藏自己的依賴(lài)關(guān)系
構(gòu)造注入
++ 更安全的代碼
++ 測(cè)試友好
++ 依賴(lài)添加代價(jià)較高焰络,顯式的表明代碼的bad smell
++ 在受影響的類(lèi)中顯式的表明依賴(lài)關(guān)系
-- 需要寫(xiě)更多的業(yè)務(wù)代碼(可以通過(guò)Lombok解決)