Spring之@Autowired依賴注入探究

前言

使用Spring,@Autowired注解肯定再熟悉不過了峭拘,今天徹底探究一下@Autowired實現(xiàn)的源碼細(xì)節(jié)

實現(xiàn)

其實這個實現(xiàn)方式其實思路很簡單:

就是在bean容器中找到type==@Autowired修飾的類型的bean姚糊,然后通過反射給屬性賦值即可

道理很簡單,但還是看代碼證實一下,并關(guān)注一些實現(xiàn)細(xì)節(jié)

例子

寫一個簡單的例子踪栋,為方便后續(xù)說明

// B Service
@Service
public class BService {
}
// A Service 通過@Autowired依賴注入 B Service
@Service
public class AService {
    @Autowired
    private BService bService;
}

@Autowired實現(xiàn)步驟

spring內(nèi)部的默認(rèn)bean工廠實現(xiàn)為DefaultListableBeanFactory敛腌,然而該BeanFactory并不支持@Autowired注解

實際上@Autowired注解的支持是依靠于ApplicationContext向DefaultListableBeanFactory注冊了Autowired后置處理器:AutowiredAnnotationBeanPostProcessor

在bean創(chuàng)建周期的populateBean(填充屬性)中會執(zhí)行該后置處理器的postProcessProperties方法來完成依賴注入

postProcessProperties

步驟如下

1.查找?guī)в蠤Autowired的屬性

findAutowiringMetadata方法查找了當(dāng)前類屬性中帶有@Autowired卧土,@Value的屬性,包裝成InjectionMetadata

findAutowiringMetadata

buildAutowiringMetadata

其中autowiredAnnotationTypes主要包含兩個注解
autowiredAnnotationTypes

二.通過beanFactory的resolveDependency方法獲取需要依賴Bean

InjectionMetadata.inject方法中像樊,調(diào)用beanFactory的resolveDependency方法

InjectionMetadata.inject1

其中desc即查找的目標(biāo)尤莺,包含了目標(biāo)的類型

三.給屬性賦值

inject方法最終獲取到依賴的bean后,反射完成屬性賦值

InjectionMetadata.inject2

到此生棍,依賴注入的功能就實現(xiàn)了

深入resolveDependency

上面的步驟都很好理解颤霎,重點是第二步通過beanFactory的resolveDependency查找依賴的bean,所以再次深入探究beanFactory是如何獲取到依賴的bean的

以上例debugger一下調(diào)用resolveDependency時的參數(shù)

debugger

beanName為“AService”即被注入的bean, desc中存放BService的一些信息: BService的類型

所以resolveDependency實際上是根據(jù)type獲取bean,即getBeanByType友酱,再深入看一下(以下代碼都是省略了分支邏輯和緩存邏輯晴音,只保留重點邏輯)

resolveDependency:

public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
    // spring一般doXXX就是實際的主干代碼,如createBean和doCreateBean
    Object result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    return result;
}

再來doResolveDependency:

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
                                  @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // 獲取依賴的type粹污,使用type去bean容器查找
    Class<?> type = descriptor.getDependencyType();
    // 查找所有符合的bean
    Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
    // 依賴的beanName
    String autowiredBeanName;
    // 依賴的bean實體
    Object instanceCandidate;
    // 一般情況只有一個段多,但多個符合的也要處理
    if (matchingBeans.size() > 1) {
        // 從多個里按優(yōu)先級選擇一個,determine即下決定
        autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
        instanceCandidate = matchingBeans.get(autowiredBeanName);
    }
    else {
        // 只有一個的情況(大部分情況)壮吩,直接取第一個
        Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
        autowiredBeanName = entry.getKey();
        instanceCandidate = entry.getValue();
    }
    // 如果返回的是class, 轉(zhuǎn)換為實體
    if (instanceCandidate instanceof Class) {
        instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
    }
    // 返回以來的bean實體
    Object result = instanceCandidate;
    return result;
}

其中重點是:

  • findAutowireCandidates:根據(jù)type查找依賴注入候選者
  • determineAutowireCandidate: 當(dāng)出現(xiàn)多候選者进苍,選擇一個(畢竟依賴注入只能注入一個對象)

findAutowireCandidates

根據(jù)type查找依賴注入候選者,先看一下它的定義

protected Map<String, Object> findAutowireCandidates(
            @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {            

其中:

  • beanName:將被填充屬性的bean的name
  • requiredType:填充的類型鸭叙,即要查找的依賴
  • DependencyDescriptor:依賴的一些描述
  • 返回一個Map<String, Object>觉啊,即beanName和bean實體的映射map(value不一定都是bean實體,有可能是實體的類)沈贝,一般情況下返回的map.size==1杠人,但也會有多個的情況

再來看下具體代碼

protected Map<String, Object> findAutowireCandidates(
        @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
    // 使用BeanFactoryUtils篩選Bean容器中的候選者
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this, requiredType, true, descriptor.isEager());
    // 初始化返回結(jié)構(gòu)
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    // 1.從resolvableDependencies中篩選候選者
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
        Class<?> autowiringType = classObjectEntry.getKey();
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = classObjectEntry.getValue();
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    // 2.從bean容器中進(jìn)一步篩選候選者
    for (String candidate : candidateNames) {
        if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
            addCandidateEntry(result, candidate, descriptor, requiredType);
        }
    }
    // 返回結(jié)果
    return result;
}

通過代碼可以看出,依賴的候選者來源有兩個地方:

  • bean容器(使用addCandidateEntry方法加入返回map)
  • resolvableDependencies(直接加入返回map)

其中bean容器的查找宋下,交給方法BeanFactoryUtils.beanNamesForTypeIncludingAncestors嗡善,那么resolvableDependencies是一個什么東西?

通過查看源碼学歧,發(fā)現(xiàn)resolvableDependencies也是一個容器罩引,存儲的是type->實體的map

resolvableDependencies

再次查找發(fā)現(xiàn)容器內(nèi)容添加基本只有一處,即registerResolvableDependency

registerResolvableDependency

registerResolvableDependency是一個public接口枝笨,實際上他的存在是為了實現(xiàn)依賴注入非Bean容器中的Bean袁铐,可以叫spring托管的外部bean

比如現(xiàn)在又個單例工具對象,你想讓其他bean可以依賴注入這個對象横浑,同時又不想把這個對象加入bean容器剔桨,則可以使用registerResolvableDependency加入到resolvableDependencies(相當(dāng)于給spring中加入一個創(chuàng)建好的對象,供其他bean依賴注入徙融,但不需要spring去創(chuàng)建管理它)

再ApplicationContext初始化階段洒缀,會把ApplicationContext對象,BeanFactory對象通過registerResolvableDependency加入到spring中托管

ApplicationContext.refresh

這就是為什么我們的bean能依賴注入上下文對象和bean工廠本身欺冀,如下

注入

當(dāng)然resolvableDependencies依然只是支線邏輯树绩,重點和大部分情況還是從bean容器中查找依賴,即BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法脚猾,這個方法返回的是一個字符串?dāng)?shù)組葱峡,也就是beanName的數(shù)組砚哗,那怎么返回候選項Object的吶龙助?

答案在addCandidateEntry方法里,看一下它的內(nèi)部

addCandidateEntry

可以看見它往findAutowireCandidates的result里面的value塞了一個根據(jù)字符串getType獲取的type(Class)而不是bean的實體,這也是為什么上一步doResolveDependency會有這么一步代碼

// 如果返回的是class, 轉(zhuǎn)換為實體
if (instanceCandidate instanceof Class) {
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}

所以findAutowireCandidates返回的map可能是beanName->bean實體提鸟,但大部分情況下是beanName->beanClass

所以重點又回到了descriptor.resolveCandidate方法军援,當(dāng)通過BeanFactoryUtils獲取候選注入bean時,返回的是一個beanName->beanClass時称勋,spring調(diào)用descriptor.resolveCandidate方法通過beanName獲取實際注入的實體(此時beanClass基本就沒啥用了)胸哥,而resolveCandidate方法內(nèi)部也很簡單,既然有了beanName赡鲜,直接根據(jù)beanName獲取bean即可:

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException {
    return beanFactory.getBean(beanName);
}

getBeanByName就不展開了空厌,這個是最基礎(chǔ)的,即通過beanName獲取bean定義银酬,初始化創(chuàng)建bean

beanNamesForTypeIncludingAncestors

先看下定義

public static String[] beanNamesForTypeIncludingAncestors(
            ListableBeanFactory lbf, Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {

根據(jù)命名:通過type查詢beanName數(shù)組,參數(shù)type即查找的類型,IncludingAncestors代表如果lbf有父級贪磺,要遞歸去祖先bean工廠中查找拧略,看下代碼

public static String[] beanNamesForTypeIncludingAncestors(
        ListableBeanFactory lbf, Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {

    Assert.notNull(lbf, "ListableBeanFactory must not be null");
    // 使用beanFactory的getBeanNamesForType方法查找
    String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
    // 如果有父級,遞歸查找
    if (lbf instanceof HierarchicalBeanFactory) {
        HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
        if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
            String[] parentResult = beanNamesForTypeIncludingAncestors(
                    (ListableBeanFactory) hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit);
            result = mergeNamesWithParent(result, parentResult, hbf);
        }
    }
    return result;
}

所以其實最核心的方法就是BeanFactory.getBeanNamesForType李破,也就是常用的方法getBean(Class<T> requiredType)內(nèi)部調(diào)用的方法宠哄,即根據(jù)type在bean容器中獲取bean,很好理解嗤攻,本文就不展開了~

總結(jié)

總結(jié)一下@Autowired的整個過程毛嫉,畫個示意圖如下


@Autowired過程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屯曹,隨后出現(xiàn)的幾起案子狱庇,更是在濱河造成了極大的恐慌,老刑警劉巖恶耽,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件密任,死亡現(xiàn)場離奇詭異,居然都是意外死亡偷俭,警方通過查閱死者的電腦和手機(jī)浪讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涌萤,“玉大人淹遵,你說我怎么就攤上這事「合” “怎么了透揣?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長川抡。 經(jīng)常有香客問我辐真,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任侍咱,我火速辦了婚禮耐床,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘楔脯。我一直安慰自己撩轰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布昧廷。 她就那樣靜靜地躺著堪嫂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪木柬。 梳的紋絲不亂的頭發(fā)上溉苛,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音弄诲,去河邊找鬼愚战。 笑死,一個胖子當(dāng)著我的面吹牛齐遵,可吹牛的內(nèi)容都是我干的寂玲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼梗摇,長吁一口氣:“原來是場噩夢啊……” “哼拓哟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伶授,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤断序,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后糜烹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體违诗,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年疮蹦,在試婚紗的時候發(fā)現(xiàn)自己被綠了诸迟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡愕乎,死狀恐怖阵苇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情感论,我是刑警寧澤绅项,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站比肄,受9級特大地震影響快耿,放射性物質(zhì)發(fā)生泄漏湿硝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一润努、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧示括,春花似錦铺浇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吼拥,卻和暖如春倚聚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凿可。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工惑折, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枯跑。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓惨驶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親敛助。 傳聞我的和親對象是個殘疾皇子粗卜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容