為什么官方不推薦使用 @Autowired?
前言
很多人剛接觸 Spring 的時候,對 @Autowired 絕對是愛得深沉。
一個注解,輕松搞定依賴注入,連代碼量都省了。
誰不愛呢?
但慢慢地,尤其是跑到稍微復雜點的項目里,@Autowired 就開始給你整點幺蛾子。
于是,官方Spring 4.0開始:不建議無腦用 @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 也不是不能用,只是你得分場景。