@Resource是Java的注解,表示一個資源,它具有雙向的含義,一個是從外部獲取一個資源,一個是向外部提供一個資源。
這其實就對應於Spring的注入和注冊。當它用在字段和方法上時,表示前者。當它用在類上時表示后者。Spring只提供了對前者的支持。
該注解本身表示的是資源,資源的含義是很寬泛的。由於絕大部分情況下在使用Spring的時候,它的容器里都是普通的bean,所以這個注解就作為bean的注入來使用了。
對於依賴注入這個事情,整體可以分為三步:
1)找出需要被注入的元素,即標了注解的字段或方法
2)根據注解的描述,在容器中找出依賴的bean
3)完成注入,即設置字段的值或進行方法調用
我們可以分析下,這里面都會遇到哪些難處理的問題:
1)找出標注解的字段和方法很簡單,使用Java反射即可。難的是如何表示它們,因為它們就是被注入的元素。
2)要在容器中找出依賴的bean,首先要知道依賴什么樣子的bean,也就是要想辦法把依賴需求給表示出來。
我們遇到的首個問題同樣是如何表示,如何描述?等把它們解決了,才會進入如何去做的環節。
舉一個例子吧,一個老師要將他們班40位同學的成績排名。他遇到的第一個問題也是如何把40個成績表示出來。
假設有三種方式吧:
1)寫到excel表格里
2)存到數據庫中
3)寫到一張紙上
再來看看這三種方式下,完成排序的手段是:
1)使用excel的排序函數
2)使用sql的order by
3)人肉排序
可見,雖然最后都達到了結果,但是不同的表示方式對應的排序手段卻相差很大,自然有的簡單,有的難。
當然,也可以把40個成績放入一個數組,然后使用類庫中現成的排序工具進行排序。
不得不說,最終又回到了數據結構和算法上來。一個是表示或描述,一個是手段或方法。
一起來看看Spring是如何描述和如何操作的。
編程新說注:還是那句話,實際的處理會比較復雜,我們還是側重從整體流程上把握。
被注入元數據
能夠被注入的元素只有字段和方法,方法又包括setter方法和普通方法。(構造方法是被單獨處理的,和此處的不混合)
所以Spring使用InjectedElement類,來表示被注入的元素,如下圖01:
對於依賴的類型,自然就是字段的類型,屬性的類型或普通方法的第一個參數類型(可以看出普通方法只能有一個參數):如下圖02:
依賴的注入,也非常簡單,是字段的話就set一下,是方法的話就invoke一下,如下圖03:
因此,關鍵是要把具體的值(也就是依賴)從容器中找出來。
以上這只是一個注入元素,一個類中可以有多個注入元素,所以還有一個以類為單位的描述。
所以Spring使用InjectionMetadata類,來表示一個類的注入元數據,如下圖04:
這里有兩個注入元素的集合,一個是Collection類型,一個是Set類型,原因在上一篇文章中解釋過了。
這兩個類是Spring的通用抽象,用作所有注入元素和注入元數據的父類。
在處理@Resource注解時對InjectedElement類進行了擴展,如下圖05:
name表示依賴的bean名稱,isDefaultName表示是否沒有顯式指定名稱,lookupType表示依賴的類型。
接下來該讀取注解@Resource的屬性,為注入元素類的字段賦值了,如下圖06:
首先獲取到這個注解,然后讀出它的name和type屬性值。如果name為空字符串,則表明沒有顯式指定名稱,那就是用默認的名稱。
如果是字段就用字段名,如果是普通方法就用方法名,如果是setter方法就用屬性名。
如果type的值是Object.class,則表明沒有顯式指定類型,那就讀取被注入元素的類型。
如果是字段就用字段類型,如果是普通方法就用第一個參數的類型,如果是setter方法就用屬性類型。
這樣就獲取到了依賴的名稱和類型。
以上這些信息都是對被注入元素的描述,下面還有對依賴的描述。
對依賴的描述
首先是注入點,InjectionPoint類,如下圖07:
如果是方法的話,現在就要具體到參數了,如果是字段的話,則還是字段。
然后是依賴描述,DependencyDescriptor類,繼承了注入點類,如下圖08:
如方法名,方法的參數以及參數索引,字段名,是否必須等等。
最后就是對依賴描述的擴展,如下圖09:
把上面獲取到的類型作為依賴描述里的依賴類型。
以上這些就是對依賴的描述,接下來就是按照這個依賴描述來從容器中找出符合的依賴。
獲取依賴的邏輯
下面是依賴的查找邏輯,如下圖10:
如果使用的是默認名稱,且容器中不包含這個名稱的bean,則按照類型去解析依賴。
否則就是顯式指定了名稱或容器中包含這個名稱的bean,則按照名稱去解析依賴。
當然,如果容器不具有自動裝配功能的話,則按照名稱去容器中獲取一個bean。
編程新說注:
在面試時,幾乎所有的人都會說@Resource是按名稱進行依賴注入的,其實是不完全正確的。
這可能是受官方文檔的影響,所以大家都是這么用的,所以總是會顯式指定注解的name屬性。
其實Spring對它的處理也支持按類型查找依賴的bean。
bean后處理器確定執行時機
最后,照例要和bean后處理器結合起來,是CommonAnnotationBeanPostProcessor這個類。
也是在postProcessMergedBeanDefinition這個方法里完成注入元數據的獲取與緩存,已備后用,如下圖11:
也是在postProcessProperties這個方法里完成依賴的注入,如下圖12:
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有沒有本質的不同?
品Spring:SpringBoot輕松取勝bean定義注冊的“第一階段”
品Spring:SpringBoot發起bean定義注冊的“二次攻堅戰”
品Spring:注解之王@Configuration和它的一眾“小弟們”
品Spring:對@PostConstruct和@PreDestroy注解的處理方法
>>> 熱門文章集錦 <<<
爸爸又給Spring MVC生了個弟弟叫Spring WebFlux
【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(干貨 | 建議珍藏)
【面試】如果你這樣回答“什么是線程安全”,面試官都會對你刮目相看(建議珍藏)
【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這么清楚的好文章(快快珍藏)
【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)
作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!