背景
項目使用的就是SpringBoot默認的結構,我看了下,依賴注入使用了最不推薦的字段注入。
字段注入
為了保持項目風格統一,省的有些理論派挑刺,還是延續字段注入的操作。
某個業務場景下,有一個抽象的父類和多個具體的子類,子類中需要用到父類注入的對象。
當即有人就說,這么寫:
public abstract class AbstractClass{
@Autowired
protected InjectedBean injectedBean;
}
it works!
但是我們看到為了只讓子類使用該對象,我們使用了protected
訪問修飾符,但這意味着,子類也可以set該對象一個新的值。基礎知識。
哦,改進一下唄
public abstract class AbstractClass{
@Autowired
protected final InjectedBean injectedBean;
}
final加持,完美的解決了問題。很可惜,不可以。
為什么呢?
Having @Autowired and final on a field are contradictory.
The latter says: this variable has one and only one value, and it's initialized at construction time.
The former says: Spring will construct the object, leaving this field as null (its default value). Then Spring will use reflection to initialize this field with a bean of type WorkspaceRepository.
If you want final fields autowired, use constructor injection
簡單來說,就是二者賦值的時機不統一造成了互斥。
構造函數注入
那我們使用上面提到的,通constructor injection
注入試試
public abstract class AbstractClass{
private InjectedBean injectedBean;
@Autowired
public void AbstractClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
Spring 不會在抽象類的構造函數上解析 @Autowired 注解。可以通過子類的構造函數注入實現。
public abstract class AbstractClass{
private InjectedBean injectedBean;
public void AbstractClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
public class ChildClass{
private InjectedBean injectedBean;
@Autowired
public void ChildClass(InjectedBean injectedBean) {
super(injectedBean);
}
}
這么寫真的有些繁瑣了...而且,就我的項目而言,我的初衷是給子類使用,這么豈不是多此一舉。
好的,也就引出了Setter注入。
Setter注入
public abstract class AbstractClass{
private LogRepository logRepository;
@Autowired
public final void setLogRepository(LogRepository logRepository) {
this.logRepository = logRepository;
}
}
Setter的時候,標記為public final
。
Getter的時候,標記為protected
。
可以說是最佳實踐了。
ref: https://segmentfault.com/a/1190000039053805
為什么不推薦使用字段注入?
事實上,當你使用IDE開發的時候,你使用了Spring的字段注入,你會得到一個提示:
Field injection is not recommended
當然是因為這種方式有好多缺點:
-
不允許不可變字段的聲明,像我們剛才說的
@Autowired protected final
-
代碼壞味道的潛在根源
因為字段注入是如此的方便,你可以“只要需要”就注入一個你想操作的對象,結果不知不覺中注入了十幾個甚至幾十個(見賢思齊,我所在的項目中確實已經有這樣的問題了)。
反之,如果我們通過構造函數注入,隨着注入對象的增多,構造函數的參數不斷變多,你明顯的就能聞到“壞味道”了,這時候你得開始想,是時候把當前類重新划分了,怎么能承擔如此多的職責呢?職責單一忘了嗎?SOLID天天盯着你們哪! -
和Spring的容器緊耦合
如果這點你不覺得有什么,那你是不是忘記寫單元測試了?
單元測試的時候你也引入了Spring框架?就是為了使用“注入”這個功能?
如果我們使用構造函數注入,或者Setter注入,我們在脫離Spring框架之后(比如單元測試)的時候,我們可以通過其他方式傳入目標對象。反之,我們只能強依賴Spring框架了。 -
隱藏了依賴
當使用依賴項注入模式時,受影響的類應該通過公開構造函數中所需的依賴項或使用方法(setter)的可選依賴項,使用公共接口清楚地公開這些依賴項。當使用基於字段的依賴項注入時,類本質上是向外部世界隱藏這些依賴項。
ref: https://blog.marcnuri.com/field-injection-is-not-recommended