非常小的一個東西,Spring依賴注入Bean型別的8種情況

2023-03-27 15:00:48

大家好,我是三友~~

今天來講一個可能看似沒有用但是實際又有點用的一個小東西,那就是@Autowired支援注入哪些Bean的型別。

為啥要講這個呢?

故事說起來可能就比較長了。

不過長話可以短說,僅僅就是突然想起來之前有一個妹子問過我這個問題!

微信公眾號:三友的java日記

1、普通物件

這沒什麼好說的,大家都這麼用的,比如需要用到UserService,直接@Autowired就可以了。

@Autowired
private UserService userService;

2、Collection及其子介面

除了支援注入一個單一的物件之外,@Autowired還支援注入一個Collection物件。

比如說,現在有個訊息通知的介面MessageNotifier

這種介面一般都會有不同的實現,比如說通過郵件通知,或者app,簡訊等等,所以就有多種實現,此時如果需要注入MessageNotifier,就可以使用注入Collection的方式,比如

@Autowired
private List<MessageNotifier> messageNotifiers;

不過這種方式有個規定,那就是注入的型別必須是Collection及其子介面,如果你直接注入一個ArrayList,那麼此時是不支援的。

3、陣列

同理,@Autowired可實現了注入一個陣列的功能。

@Autowired
private MessageNotifier[] messageNotifiers;

程式碼如下:

4、Map

同樣的,@Autowired還可以注入一個Map。

@Autowired
private Map<String, MessageNotifier> messageNotifierMap;

此時注入的map,key的型別就是bean的名稱,這種方式可以配合策略模式使用。

不過,這種方式只支援注入的是Map介面,不支援子型別介面,程式碼如下。

5、@Lazy

當一個注入的欄位加了@Lazy註解之後,那麼此時就代表這個欄位是延遲注入。

@Autowired
@Lazy
private MessageNotifier messageNotifier;

延遲注入並不是不注入,而是注入目標物件型別的代理物件,真正的目標是當需要用到的時候在建立。

如圖所示,當注入的MessageNotifier時加了@Lazy註解,那麼此時注入的其實是MessageNotifier的代理物件,而真正的MessageNotifier物件並沒有建立,圖中代理物件我稱為MessageNotifierProxy

由於注入的是物件是代理物件MessageNotifierProxy,那麼真正被使用的就是MessageNotifierProxy,一旦呼叫了MessageNotifierProxy的方法,此時MessageNotifierProxy會去Spring容器中查詢真正的MessageNotifier物件,然後再呼叫MessageNotifier物件的方法。

程式碼如下:

這就是@Lazy延遲注入的原理。並不是不注入,而是注入一個代理物件,可以理解為一個預留位置,一個空殼子,先佔著位置,等用到這個殼子的時候,這個殼子會去查詢到真正的物件,呼叫真正物件的方法。

@Lazy的一個使用場景就是用來解決Spring無法處理的迴圈依賴場景,比如使用了@Async註解的迴圈依賴的場景,不瞭解的小夥伴可以看一下 @Async註解的坑,小心 這篇文章

6、Optional

Optional是JDK1.8提供的一個api,可以優雅的解決判空的問題。

@Autowired也支援了注入Optional型別。

@Autowired
private Optional<MessageNotifier> messageNotifier;

程式碼如下:

注入Optional這種方式可以解決注入的物件不存在的導致異常問題,也就是安全注入。

比如說,MessageNotifier這個物件Spring容器中並沒有,如果直接注入,此時會拋NoSuchBeanDefinitionException異常

而直接通過注入Optional的方式就可以解決這個問題。

除了通過Optional的方式之外,也可以直接把@Autowired的required的屬性設定為false來解決注入物件不存在的問題。

那Optional存在的作用是啥?

其實Optional的作用僅僅是不用寫為空的判斷,這也是Optional這個類的作用作用,除了這個,跟直接@Autowired物件並沒有其它區別。

注入Optional這種方式其實用的不多,在我的映像中,我在原始碼中幾乎沒有看見這種注入方式。

7、ObjectFactory和ObjectProvider

ObjectFactory和ObjectProvider是Spring提供的兩介面

ObjectFactory
ObjectFactory

ObjectProvider繼承了ObjectFactory

ObjectProvider
ObjectProvider

@Autowired也可以直接注入這兩個介面。

@Autowired
private ObjectFactory<MessageNotifier> messageNotifierObjectFactory;

@Autowired
private ObjectProvider<MessageNotifier> messageNotifierObjectProvider;

程式碼如下:

從這段程式碼也可以看出,最終注入的其實是DependencyObjectProvider實現。

ObjectFactory也是用來做延遲注入的操作,跟@Lazy作用差不多,但是實現原理不一樣。

用上面的例子來說,注入ObjectFactory的時候並有建立MessageNotifier物件。

當需要使用MessageNotifier的時候需要通過ObjectFactory的getObject方法獲取,此時才會真正建立MessageNotifier物件。

MessageNotifier messageNotifier = messageNotifierObjectFactory.getObject();
getObject
getObject

所以@Async註解導致的迴圈依賴異常不僅可以通過@Lazy註解解決,也可以通過注入ObjectFactory的方式解決。

同理,ObjectProvider也有延遲載入的功能,但是除了延遲載入之外,ObjectProvider額外提供了跟Optional安全注入的功能,這個功能ObjectFactory是沒有的。

上面的例子中,當使用ObjectFactory的getObject方法時,如果Spring容器中不存在MessageNotifier物件,此時也會拋NoSuchBeanDefinitionException異常。

但是ObjectProvider額外提供的getIfAvailable方法就支援獲取不存在的物件的功能,當通過getIfAvailable獲取的物件不存在時,只會返回null,並不會出拋異常。

ObjectFactory和ObjectProvider在框架內部中使用的還是比較多的。

就比如說,在MybatisPlus自動裝配的時候就大量使用ObjectProvider

並且泛型型別就是陣列或者是集合,跟前面說的都對應上了。

通過這種方式就可以安全的注入,當Spring容器有這些物件的時候MybatisPlus就使用這些,沒有也不會報錯。

8、JSR-330 Provider

首先,來講一下什麼是JSR-330。

JSR是Java Specification Requests的縮寫,是一種Java標準規範。

而330算是一個版本,除了330,聽到的比較多的還有250。

這個規範定義了一些IOC的註解,我們熟知的比如@Resource、@PostConstruct、@PreDestroy註解都是JSR-250中提出的。

一些IOC的框架會基於這個標準來實現這些介面的功能,比如Spring、Dagger2等IOC框架都實現了這些註解的功能。

所以,如果你不使用Spring框架,使用其它的IOC框架,那麼@Resource、@PostConstruct、@PreDestroy註解都是可以生效的。

在JSR-330中,提出了javax.inject.Provider這個介面

不過,想使用JSR-330這個介面,需要引入依賴

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Spring也支援注入這個型別的介面

這個介面的功能跟前面提到的ObjectFactory功能是一樣的,也支援延遲注入的功能。

總結

到這Spring能夠注入的Bean的8種型別就講完了,其實這8種型別可以分為以下幾種功能:

  • 單一注入,就是注入一個單一的物件
  • 集合注入,可以注入陣列或者集合
  • 延遲注入,比如@Lazy、ObjectFactory、ObjectProvider、JSR-330 Provider
  • 安全注入,不存在不會拋異常,比如Optional、ObjectProvider

這幾種方式並不是互斥的,比如說延遲注入也可以注入的是一個集合,前面舉的MyBaisPlus自動裝配時ObjectProvider的使用就是很好的例子。

同時雖然本文舉例的是@Autowird註解和欄位注入的方式,但上面提到的注入的Bean型別跟使用註解和注入方式沒什麼關係,@Resource註解,構造器注入,setter注入都是一樣的。

往期熱門文章推薦

為什麼RocketMQ會重複消費,我扒出了7種原因

如何去閱讀原始碼,我總結了18條心法

如何實現延遲任務,我總結了11種方法

如何寫出漂亮程式碼,我總結了45個小技巧

三萬字盤點Spring/Boot的那些常用擴充套件點

兩萬字盤點那些被玩爛了的設計模式

掃碼或者搜尋關注公眾號 三友的java日記 ,及時乾貨不錯過,公眾號致力於通過畫圖加上通俗易懂的語言講解技術,讓技術更加容易學習,回覆 面試 即可獲得一套面試真題。