為什么官方不推薦使用 @Autowired?
前言
很多人剛接觸 Spring 的時(shí)候,對(duì) @Autowired 絕對(duì)是愛(ài)得深沉。
一個(gè)注解,輕松搞定依賴(lài)注入,連代碼量都省了。
誰(shuí)不愛(ài)呢?
但慢慢地,尤其是跑到稍微復(fù)雜點(diǎn)的項(xiàng)目里,@Autowired 就開(kāi)始給你整點(diǎn)幺蛾子。
于是,官方Spring 4.0開(kāi)始:不建議無(wú)腦用 @Autowired,而是更推薦構(gòu)造函數(shù)注入。
圖片
為什么?
是 @Autowired 不行嗎?并不是。
它可以用,但問(wèn)題是:它不是無(wú)敵的,濫用起來(lái)容易埋坑。
下面就來(lái)聊聊為啥官方建議你慎用 @Autowired,順便再帶點(diǎn)代碼例子,希望對(duì)你會(huì)有所幫助。
1. 容易導(dǎo)致隱式依賴(lài)
很多小伙伴在工作中喜歡直接寫(xiě):
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
}
看著挺簡(jiǎn)單,但問(wèn)題來(lái)了:類(lèi)的依賴(lài)關(guān)系藏得太深了。
- 你看這段代碼,MyService 和 MyRepository 的關(guān)系其實(shí)是個(gè)“隱形依賴(lài)”,全靠 @Autowired 來(lái)注入。
- 如果有個(gè)同事剛接手代碼,打開(kāi)一看,完全不知道 myRepository 是啥玩意兒、怎么來(lái)的,只有通過(guò) IDE 或運(yùn)行時(shí)才能猜出來(lái)。
隱式依賴(lài)的結(jié)果就是,代碼看起來(lái)簡(jiǎn)單,但維護(hù)起來(lái)費(fèi)勁。
后期加個(gè)新依賴(lài),或者改依賴(lài)順序,分分鐘把人搞糊涂。
怎么破?
用 構(gòu)造函數(shù)注入 替代。
@Service
public class MyService {
private final MyRepository myRepository;
// 構(gòu)造函數(shù)注入,依賴(lài)一目了然
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
這樣做的好處是:
- 依賴(lài)清晰:誰(shuí)依賴(lài)誰(shuí),直接寫(xiě)在構(gòu)造函數(shù)里,明明白白。
- 更易測(cè)試:構(gòu)造函數(shù)注入可以手動(dòng)傳入 mock 對(duì)象,方便寫(xiě)單元測(cè)試。
2. 會(huì)導(dǎo)致強(qiáng)耦合
再舉個(gè)例子,很多人喜歡直接用 @Autowired 注入具體實(shí)現(xiàn)類(lèi),比如:
@Service
public class MyService {
@Autowired
private SpecificRepository specificRepository;
}
表面上沒(méi)毛病,但這是硬邦邦地把 MyService 和 SpecificRepository 綁死了。
萬(wàn)一有一天,業(yè)務(wù)改了,需要切換成另一個(gè)實(shí)現(xiàn)類(lèi),比如 AnotherSpecificRepository,你得改代碼、改注解,連帶著測(cè)試也崩。
怎么破?
用接口和構(gòu)造函數(shù)注入,把依賴(lài)解耦。
@Service
public class MyService {
private final Repository repository;
public MyService(Repository repository) {
this.repository = repository;
}
}
然后通過(guò) Spring 的配置文件或者 @Configuration 類(lèi)配置具體實(shí)現(xiàn):
@Configuration
public class RepositoryConfig {
@Bean
public Repository repository() {
return new SpecificRepository();
}
}
這么搞的好處是:
- 靈活切換:改實(shí)現(xiàn)類(lèi)時(shí),不用動(dòng)核心邏輯代碼。
- 符合面向接口編程的思想:降低耦合,提升可擴(kuò)展性。
3. 容易導(dǎo)致 NullPointerException
有些小伙伴喜歡這么寫(xiě):
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public void doSomething() {
myRepository.save(); // 啪!NullPointerException
}
}
問(wèn)題在哪?如果 Spring 容器還沒(méi)來(lái)得及注入依賴(lài),你的代碼就跑了(比如在構(gòu)造函數(shù)或初始化方法中直接調(diào)用依賴(lài)),結(jié)果自然就是 NullPointerException。
怎么破?
用構(gòu)造函數(shù)注入,徹底干掉 null 的可能性。
@Service
public class MyService {
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository; // 確保依賴(lài)在對(duì)象初始化時(shí)就已注入
}
public void doSomething() {
myRepository.save();
}
}
構(gòu)造函數(shù)注入的另一個(gè)優(yōu)點(diǎn)是:依賴(lài)注入是強(qiáng)制的,Spring 容器不給你注入就報(bào)錯(cuò),讓問(wèn)題早暴露。
4.自動(dòng)裝配容易搞出迷惑行為
Spring 的自動(dòng)裝配機(jī)制有時(shí)候是“黑魔法”,尤其是當(dāng)你的項(xiàng)目里有多個(gè)候選 Bean 時(shí)。比如:
@Service
public class MyService {
@Autowired
private Repository repository; // 容器里有兩個(gè) Repository 實(shí)現(xiàn)類(lèi),咋辦?
}
如果有兩個(gè)實(shí)現(xiàn)類(lèi),比如 SpecificRepository 和 AnotherRepository,Spring 容器直接報(bào)錯(cuò)。解決方法有兩種:
- 指定 @Primary。
- 用 @Qualifier 手動(dòng)指定。
但這些方式都讓代碼看起來(lái)更復(fù)雜了,還可能踩坑。
怎么破?
構(gòu)造函數(shù)注入 + 顯式配置。
@Configuration
public class RepositoryConfig {
@Bean
public Repository repository() {
return new SpecificRepository();
}
}
你明確告訴 Spring 該用哪個(gè)實(shí)現(xiàn)類(lèi),別讓容器幫你猜,省得以后“配錯(cuò)藥”。
5. 寫(xiě)單元測(cè)試非常痛苦
最后,聊聊測(cè)試的事兒。
@Autowired 依賴(lài) Spring 容器才能工作,但寫(xiě)單元測(cè)試時(shí),大家都不想起 Spring 容器(麻煩、慢)。結(jié)果就是:
- 字段注入:沒(méi)法手動(dòng)傳入 mock 對(duì)象。
- 自動(dòng)裝配:有時(shí)候不清楚用的 Bean 是哪個(gè),測(cè)試難搞。
怎么破?
構(gòu)造函數(shù)注入天生就是為單元測(cè)試設(shè)計(jì)的。
public class MyServiceTest {
@Test
public void testDoSomething() {
MyRepository mockRepository = mock(MyRepository.class);
MyService myService = new MyService(mockRepository);
// 測(cè)試邏輯
}
}
看見(jiàn)沒(méi)?
直接傳入 mock 對(duì)象,測(cè)試簡(jiǎn)單、優(yōu)雅。
總結(jié)
簡(jiǎn)單總結(jié)下問(wèn)題:
- 隱式依賴(lài)讓代碼可讀性差。
- 強(qiáng)耦合違背面向接口編程。
- 字段注入容易 NPE。
- 自動(dòng)裝配有坑。
- 單元測(cè)試不好寫(xiě)。
那到底咋辦?用 構(gòu)造函數(shù)注入,清晰、穩(wěn)健、測(cè)試友好,官方推薦不是沒(méi)道理的。
但話(huà)說(shuō)回來(lái),@Autowired 也不是不能用,只是你得分場(chǎng)景。