為什麼Spring官方不推薦使用 @Autowired?

苏三说技术發表於2024-11-29

前言

很多人剛接觸 Spring 的時候,對 @Autowired 絕對是愛得深沉。

一個註解,輕鬆搞定依賴注入,連程式碼量都省了。

誰不愛呢?

但慢慢地,尤其是跑到稍微複雜點的專案裡,@Autowired 就開始給你整點么蛾子。

於是,官方在某些文件和社群交流中提到過:不建議無腦用 @Autowired,而是更推薦建構函式注入。

為什麼?是 @Autowired 不行嗎?並不是。

它可以用,但問題是:它不是無敵的,濫用起來容易埋坑。

下面就來聊聊為啥官方建議你慎用 @Autowired,順便再帶點程式碼例子,希望對你會有所幫助。

1. 容易導致隱式依賴

很多小夥伴在工作中喜歡直接寫:

@Service
public class MyService {
    @Autowired
    private MyRepository myRepository;
}

看著挺簡單,但問題來了:類的依賴關係藏得太深了

  • 你看這段程式碼,MyServiceMyRepository 的關係其實是個“隱形依賴”,全靠 @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;
}

表面上沒毛病,但這是硬邦邦地把 MyServiceSpecificRepository 綁死了。

萬一有一天,業務改了,需要切換成另一個實現類,比如 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 實現類,咋辦?
}

如果有兩個實現類,比如 SpecificRepositoryAnotherRepository,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 物件,測試簡單、優雅。

總結

簡單總結下問題:

  1. 隱式依賴讓程式碼可讀性差。
  2. 強耦合違背面向介面程式設計。
  3. 欄位注入容易 NPE。
  4. 自動裝配有坑。
  5. 單元測試不好寫。

那到底咋辦?用 建構函式注入,清晰、穩健、測試友好,官方推薦不是沒道理的。

但話說回來,@Autowired 也不是不能用,只是你得分場景。

開發中,養成用建構函式注入的習慣,能讓你的程式碼更健壯,少挖坑,多幹活!

最後說一句(求關注,別白嫖我)

如果這篇文章對您有所幫助,或者有所啟發的話,幫忙關注一下我的同名公眾號:蘇三說技術,您的支援是我堅持寫作最大的動力。
求一鍵三連:點贊、轉發、在看。
關注公眾號:【蘇三說技術】,在公眾號中回覆:進大廠,可以免費獲取我最近整理的10萬字的面試寶典,好多小夥伴靠這個寶典拿到了多家大廠的offer。

相關文章