當前位置: 妍妍網 > 碼農

停止在 SpringBoot 中使用欄位註入!

2024-04-01碼農

大家好,我是鵬磊。

在 Spring Boot 依賴項註入的上下文中,存在關於註入依賴項最佳實踐的爭論:欄位註入、Setter註入和建構函式註入。

在本文中,我們將重點討論欄位註入的缺陷,並提出一個遠離它的案例。

什麽是欄位註入?

欄位註入涉及直接用 @Autowired 註釋類的私有欄位。這是一個例子:

@Component
public classOrderService{
@Autowired
private OrderRepository orderRepository;
public Order findOrderById(Long id){
return orderRepository.findById(id);
}
}

為什麽應該停止使用欄位註入

1. 可測試性

欄位註入使元件的單元測試變得復雜。 由於依賴項直接註入到欄位中,因此我們無法在 Spring 上下文之外輕松提供模擬或替代實作。

讓我們以 sameOrderService 類為例。

如果我們希望對 OrderService 進行單元測試,那麽在模擬 OrderRepository 時會遇到困難,因為它是一個私有欄位。下面是對 OrderService 進行單元測試的方法:

如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

@RunWith(SpringJUnit4 classRunner. class)
public classOrderServiceTest
{
private OrderService orderService;
@Mock
private OrderRepository orderRepository;
@Before
publicvoidsetUp()throws Exception {
orderService = new OrderService();
// This will set the mock orderRepository into orderService's private field
ReflectionTestUtils.setField(orderService, "orderRepository", orderRepository);
}
...
}



盡管可以實作,但使用反射來替換私有欄位並不是一個很好的設計。它違背了物件導向的設計原則,使測試難以閱讀和維護。

但是,如果我們使用建構函式註入:

@Component
public classOrderService{
privatefinal OrderRepository orderRepository;
publicOrderService(OrderRepository orderRepository){
this.orderRepository = orderRepository;
}
}

我們可以在測試期間輕松提供模擬 OrderRepository:

OrderRepository mockRepo = mock(OrderRepository. class);
OrderService orderService = new OrderService(mockRepo);

2. 不變性

欄位註入使我們的 Bean 在構建後可變。而透過建構函式註入,一旦構造了一個物件,它的依賴關系就會保持不變。

舉例來說:

欄位註入類:

@Component
public classUserService{
@Autowired
private UserRepository userRepository;
}

這裏,userRepository 在建立物件後可以重新分配參照,這就打破了不變性原則。

如果我們使用建構函式註入:

@Component
public classUserService{
privatefinal UserRepository userRepository;
publicUserService(UserRepository userRepository){
this.userRepository = userRepository;
}
}

該 userRepository 欄位可以聲明為最終欄位,在構造完成後,就會一直保持不變。

3.與Spring更緊密的耦合

欄位註入使我們的類與 Spring 耦合更緊密,因為它直接在我們的欄位上使用 Spring 特定的註釋 ( @Autowired)。這可能會在以下場景中出現問題:

不使用 Spring 的情況 :假設我們正在構建一個不使用 Spring 的輕量級命令列應用程式,但我們仍然想利用 UserService 的邏輯。在這種情況下,@Autowired 註釋沒有任何意義,不能用於註入依賴項。我們就必須重構該類或實作繁瑣的解決方法才能重用UserService.

如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

切換到另一個 DI 框架 :如果我們決定切換到另一個依賴註入框架,比如 Google Guice,Spring 特定的框架 @Autowired 就會成為一個障礙。那時我們必須重構使用 Spring 特定註釋的每一個地方,這會是十分繁瑣的。

可讀性和理解性 :對於不熟悉 Spring 的開發人員來說,遇到 @Autowired 註解可能會感到困惑。他們可能想知道如何解決依賴關系,從而增加學習成本(ps:雖然不熟悉 Spring 開發的Java程式設計師可能很少了)。

4. 空指標異常

當類利用欄位註入並透過其預設建構函式例項化時,依賴欄位保持未初始化。

舉例來講:

@Component
public classPaymentGateway{
@Autowired
private PaymentQueue paymentQueue;
publicvoidinitiate(PaymentRequest request){
paymentQueue.add(request);
...
}
}
public classPaymentService{
publicvoidprocess(PaymentRequest request){
PaymentGateway gateway = new PaymentGateway();
gateway.initiate(request);
}
}

透過上面的程式碼,我們不難看出,如果在執行時以這種狀態存取PaymentGateway,則會發生 NullPointerException。在Spring上下文之外手動初始化這些欄位的唯一方法是使用反射,反射機制的語法比較繁瑣且易錯,在程式可讀性方面存在一定問題,所以不建議這樣做。

5. 迴圈依賴

欄位註入可能會掩蓋迴圈依賴問題,使它們在開發過程中更難被發現。

舉例來講:

考慮兩個相互依賴的服務AService和BService:

@Service
public classAService{
@Autowired
private BService bService;
}
@Service
public classBService{
@Autowired
private AService aService;
}

以上可能會導致應用程式中出現意想不到的問題。

使用建構函式註入,Spring會在啟動期間立即丟擲 BeanCurrentlyInCreationException,讓我們意識到迴圈依賴。不過,要解決迴圈依賴問題,可以使用@Lazy延遲載入其中一個依賴項。

結論

雖然欄位註入可能看起來更簡潔,但它的缺點遠遠超過了它的簡潔性。建構函式註入在應用程式的可測試性、不變性和整體穩健性方面提供了明顯的優勢。它與 SOLID 原則非常一致,確保我們的 Spring Boot 應用程式可維護且不易出錯。

所以,建議大家停止在 Spring Boot 中使用欄位註入!

🔥 磊哥私藏精品 熱門推薦 🔥