前言
很多人剛接觸 Spring 的時候,對 @Autowired
絕對是愛得深沉。
一個註解,輕鬆搞定依賴注入,連程式碼量都省了。
誰不愛呢?
但慢慢地,尤其是跑到稍微複雜點的專案裡,@Autowired
就開始給你整點么蛾子。
於是,官方在某些文件和社群交流中提到過:不建議無腦用 @Autowired
,而是更推薦建構函式注入。
為什麼?是 @Autowired
不行嗎?並不是。
它可以用,但問題是:它不是無敵的,濫用起來容易埋坑。
下面就來聊聊為啥官方建議你慎用 @Autowired
,順便再帶點程式碼例子,希望對你會有所幫助。
1. 容易導致隱式依賴
很多小夥伴在工作中喜歡直接寫:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
}
看著挺簡單,但問題來了:類的依賴關係藏得太深了。
- 你看這段程式碼,
MyService
和MyRepository
的關係其實是個“隱形依賴”,全靠@Autowired
來注入。 - 如果有個同事剛接手程式碼,開啟一看,完全不知道
myRepository
是啥玩意兒、怎麼來的,只有透過 IDE 或執行時才能猜出來。
隱式依賴的結果就是,程式碼看起來簡單,但維護起來費勁。
後期加個新依賴,或者改依賴順序,分分鐘把人搞糊塗。
怎麼破?
用 建構函式注入 替代。
@Service
public class MyService {
private final MyRepository myRepository;
// 建構函式注入,依賴一目瞭然
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
這樣做的好處是:
- 依賴清晰:誰依賴誰,直接寫在建構函式里,明明白白。
- 更易測試:建構函式注入可以手動傳入 mock 物件,方便寫單元測試。
2. 會導致強耦合
再舉個例子,很多人喜歡直接用 @Autowired
注入具體實現類,比如:
@Service
public class MyService {
@Autowired
private SpecificRepository specificRepository;
}
表面上沒毛病,但這是硬邦邦地把 MyService
和 SpecificRepository
綁死了。
萬一有一天,業務改了,需要切換成另一個實現類,比如 AnotherSpecificRepository
,你得改程式碼、改註解,連帶著測試也崩。
怎麼破?
用介面和建構函式注入,把依賴解耦。
@Service
public class MyService {
private final Repository repository;
public MyService(Repository repository) {
this.repository = repository;
}
}
然後透過 Spring 的配置檔案或者 @Configuration
類配置具體實現:
@Configuration
public class RepositoryConfig {
@Bean
public Repository repository() {
return new SpecificRepository();
}
}
這麼搞的好處是:
- 靈活切換:改實現類時,不用動核心邏輯程式碼。
- 符合面向介面程式設計的思想:降低耦合,提升可擴充套件性。
3. 容易導致 NullPointerException
有些小夥伴喜歡這麼寫:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public void doSomething() {
myRepository.save(); // 啪!NullPointerException
}
}
問題在哪?如果 Spring 容器還沒來得及注入依賴,你的程式碼就跑了(比如在建構函式或初始化方法中直接呼叫依賴),結果自然就是 NullPointerException
。
怎麼破?
用建構函式注入,徹底幹掉 null
的可能性。
@Service
public class MyService {
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository; // 確保依賴在物件初始化時就已注入
}
public void doSomething() {
myRepository.save();
}
}
建構函式注入的另一個優點是:依賴注入是強制的,Spring 容器不給你注入就報錯,讓問題早暴露。
4.自動裝配容易搞出迷惑行為
Spring 的自動裝配機制有時候是“黑魔法”,尤其是當你的專案裡有多個候選 Bean 時。比如:
@Service
public class MyService {
@Autowired
private Repository repository; // 容器裡有兩個 Repository 實現類,咋辦?
}
如果有兩個實現類,比如 SpecificRepository
和 AnotherRepository
,Spring 容器直接報錯。解決方法有兩種:
- 指定
@Primary
。 - 用
@Qualifier
手動指定。
但這些方式都讓程式碼看起來更復雜了,還可能踩坑。
怎麼破?
建構函式注入 + 顯式配置。
@Configuration
public class RepositoryConfig {
@Bean
public Repository repository() {
return new SpecificRepository();
}
}
你明確告訴 Spring 該用哪個實現類,別讓容器幫你猜,省得以後“配錯藥”。
5. 寫單元測試非常痛苦
最後,聊聊測試的事兒。
@Autowired
依賴 Spring 容器才能工作,但寫單元測試時,大家都不想起 Spring 容器(麻煩、慢)。結果就是:
- 欄位注入:沒法手動傳入 mock 物件。
- 自動裝配:有時候不清楚用的 Bean 是哪個,測試難搞。
怎麼破?
建構函式注入天生就是為單元測試設計的。
public class MyServiceTest {
@Test
public void testDoSomething() {
MyRepository mockRepository = mock(MyRepository.class);
MyService myService = new MyService(mockRepository);
// 測試邏輯
}
}
看見沒?
直接傳入 mock 物件,測試簡單、優雅。
總結
簡單總結下問題:
- 隱式依賴讓程式碼可讀性差。
- 強耦合違背面向介面程式設計。
- 欄位注入容易 NPE。
- 自動裝配有坑。
- 單元測試不好寫。
那到底咋辦?用 建構函式注入,清晰、穩健、測試友好,官方推薦不是沒道理的。
但話說回來,@Autowired
也不是不能用,只是你得分場景。
開發中,養成用建構函式注入的習慣,能讓你的程式碼更健壯,少挖坑,多幹活!
最後說一句(求關注,別白嫖我)
如果這篇文章對您有所幫助,或者有所啟發的話,幫忙關注一下我的同名公眾號:蘇三說技術,您的支援是我堅持寫作最大的動力。
求一鍵三連:點贊、轉發、在看。
關注公眾號:【蘇三說技術】,在公眾號中回覆:進大廠,可以免費獲取我最近整理的10萬字的面試寶典,好多小夥伴靠這個寶典拿到了多家大廠的offer。