【Spring】@Autowired 注入類的集合類型深度解剖

Spring是你可靠的大管家。


先看demo,放碼過來

  1. 有一個(gè)用戶實(shí)體類User疹娶,包含姓名name、年齡age伦连、電話mobile三個(gè)屬性雨饺。
/**
 * 用戶實(shí)體
 *
 * @author weixiaoyu
 * @date 2018/4/27
 */
public class User {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年齡
     */
    private Integer age;
    /**
     * 電話
     */
    private String mobile;
    /**
     * 構(gòu)造方法
     *
     * @param name
     * @param age
     * @param mobile
     */
    public User(String name, Integer age, String mobile) {
        this.name = name;
        this.age = age;
        this.mobile = mobile;
    }

    /* setter & getter */
}
  1. 有一個(gè)接口BasePrintService,是打印信息基礎(chǔ)服務(wù)類除师,接口中有一個(gè)print打印信息方法沛膳,方法的參數(shù)為上面定義的User用戶類扔枫。
/**
 * 打印信息基礎(chǔ)服務(wù)類
 *
 * @author weixiaoyu1
 * @date 2019/1/16
 */
public interface BasePrintService {
    /**
     * 打印
     *
     * @param user
     */
    void print(User user);
}
  1. BasePrintService接口有三個(gè)實(shí)現(xiàn)類PrintNameServiceImplPrintAgeServiceImplPrintMobileServiceImpl短荐,分別實(shí)現(xiàn)了打印名稱倚舀、打印年齡和打印電話的功能。
    注意:這三個(gè)實(shí)現(xiàn)類上方都標(biāo)明了@Service注解忍宋,表明服務(wù)Bean交給Spring進(jìn)行管理痕貌。
/**
 * 打印名稱服務(wù)實(shí)現(xiàn)類
 *
 * @author weixiaoyu1
 * @date 2019/1/16
 */
@Service
public class PrintNameServiceImpl implements BasePrintService {
    /**
     * 打印名稱
     *
     * @param user
     */
    @Override
    public void print(User user) {
        System.out.println("打印名稱 ---> User.name = " + user.getName());
    }
}
/**
 * 打印年齡服務(wù)實(shí)現(xiàn)類
 *
 * @author weixiaoyu1
 * @date 2019/1/16
 */
@Service
public class PrintAgeServiceImpl implements BasePrintService {
    /**
     * 打印年齡
     *
     * @param user
     */
    @Override
    public void print(User user) {
        System.out.println("打印年齡 ---> User.age = " + user.getAge().toString());
    }
}
/**
 * 打印電話服務(wù)實(shí)現(xiàn)類
 *
 * @author weixiaoyu1
 * @date 2019/1/16
 */
@Service
public class PrintMobileServiceImpl implements BasePrintService {
    /**
     * 打印電話
     *
     * @param user
     */
    @Override
    public void print(User user) {
        System.out.println("打印電話 ---> User.mobile = " + user.getMobile());
    }
}
  1. 測試類,注釋都寫的很清楚哈
/**
 * 測試打印類
 *
 * @author weixiaoyu1
 * @date 2019/1/16
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = PpValidatorApplication.class)
public class PrintServiceTest {
    /**
     * '將所有類型為BasePrintService的Bean注入為一個(gè)List
     */
    @Autowired
    private List<BasePrintService> printServiceList;
    /**
     * '將所有類型為BasePrintService的Bean注入為一個(gè)Map
     * key為Bean的name
     */
    @Autowired
    private Map<String, BasePrintService> printServiceMap;

    /**
     * 測試方法糠排,遍歷List舵稠,執(zhí)行每一個(gè)Bean的print方法
     */
    @Test
    public void testPrintList() {
        User user = new User("LittleRain", 25, "13010101100");
        for (BasePrintService printService : printServiceList) {
            printService.print(user);
        }
    }

    /**
     * 測試方法,遍歷Map入宦,先打印Map的key(也就是Bean的name)哺徊,再執(zhí)行每一個(gè)Bean的print方法
     */
    @Test
    public void testPrintMap() {
        User user = new User("LittleRain", 25, "13010101100");
        for (String beanName : printServiceMap.keySet()) {
            System.out.println("打印實(shí)現(xiàn)類beanName = " + beanName);
            printServiceMap.get(beanName).print(user);
        }
    }
}
  • 通過@Autowired將所有類型為BasePrintService的Bean注入為ListList<BasePrintService>
  • 將所有類型為BasePrintService的Bean注入為MapMap<String, BasePrintService>乾闰,
    Map的 key 為Bean的 name落追。
  • 調(diào)用測試類的testPrintList()方法,遍歷List涯肩,依次執(zhí)行每一個(gè)類型為BasePrintService的Bean的print方法轿钠,執(zhí)行結(jié)果如下:
打印年齡 ---> User.age = 25
打印電話 ---> User.mobile = 13010101100
打印名稱 ---> User.name = LittleRain
  • 調(diào)用測試類的testPrintMap()方法巢钓,遍歷Map,先打印Map的key(也就是類型為BasePrintService的Bean的name)疗垛,再依次執(zhí)行每一個(gè)Bean的print方法症汹,執(zhí)行結(jié)果如下:
打印實(shí)現(xiàn)類beanName = printAgeServiceImpl
打印年齡 ---> User.age = 25
打印實(shí)現(xiàn)類beanName = printMobileServiceImpl
打印電話 ---> User.mobile = 13010101100
打印實(shí)現(xiàn)類beanName = printNameServiceImpl
打印名稱 ---> User.name = LittleRain

demo總結(jié):

  • 所有實(shí)現(xiàn)了BasePrintService接口的實(shí)現(xiàn)類PrintNameServiceImplPrintAgeServiceImplPrintMobileServiceImpl贷腕,通過@Service注解烈菌,將Bean交給Spring進(jìn)行管理。
    此時(shí)Bean的name為類名的首字母小寫花履,type為BasePrintService芽世。
  • 通過Spring提供的@Autowired注解,可以將所有類型為BasePrintService的Bean注入為一個(gè)List诡壁,也可以注入為一個(gè)Map济瓢,此時(shí)Map的 key 為Bean的 name
  • 所有Bean的配置和注入都交給Spring去管理妹卿,通過@Autowired注解可以一次性的將所有滿足條件的Bean注入到一個(gè)集合中旺矾。
  • 這種方式提供了代碼設(shè)計(jì)上更多花樣的可行性,咱們下篇文章再細(xì)說(提前占個(gè)坑)~

提問

為什么可以通過@Autowired注解可以一次性的將所有滿足條件的Bean注入到一個(gè)集合中呢夺克?具體Spring又是如何實(shí)現(xiàn)的呢箕宙?為神馬?铺纽?柬帕?


那么,開始扒源碼吧

  1. 我們知道可以通過注解@Service等將Bean注冊到Spring容器中狡门,交給Spring進(jìn)行管理陷寝。

具體處理過程可以看這篇文章:Spring對(duì)注解(Annotation)處理源碼分析1——掃描和讀取Bean定義

畫重點(diǎn),管理注解Bean定義的容器AnnotationConfigApplicationContext直接注冊其馏、或掃描指定的包及其子包后調(diào)用注冊方法凤跑,將Bean進(jìn)行一系列處理過后注冊到BeanFactory的HashMap中。

  1. 又知道可以通過使用getBean方法向Spring容器的BeanFactory索取被管理的Bean叛复。

具體處理過程可以看這篇文章:《Spring技術(shù)內(nèi)幕》學(xué)習(xí)筆記5——IoC容器的依賴注入

追蹤實(shí)現(xiàn)了BeanFactorygetBean方法的AbstractApplicationContext

@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

調(diào)用了AbstractBeanFactorydoGetBean方法

@Override
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

doGetBean方法是實(shí)際干活的仔引,里面重要的一步就是createBean方法創(chuàng)建Bean。

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

我們跳進(jìn)AbstractAutowireCapableBeanFactorycreateBean方法褐奥,里面真正干活的是 doCreateBean方法咖耘,創(chuàng)建了Bean實(shí)例。

Object beanInstance = doCreateBean(beanName, mbdToUse, args);

doCreateBean方法里面有兩個(gè)重要的步驟抖僵, createBeanInstancepopulateBean鲤看。

// 生成Bean所包含的java對(duì)象實(shí)例
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 對(duì)Bean屬性的依賴注入進(jìn)行處理
populateBean(beanName, mbd, instanceWrapper);

createBeanInstance方法生成Bean所包含的java對(duì)象實(shí)例,我們就不往里深扒了耍群。
重點(diǎn)populateBean方法义桂,給剛創(chuàng)建的實(shí)例處理屬性的依賴注入找筝。
我們直接定位到關(guān)鍵代碼。

pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);

我們知道容器對(duì)Bean對(duì)象依賴注入時(shí)慷吊,是通過容器中注冊的 Bean后置處理器 處理這些注解的袖裕。而對(duì)于@Autowired注解,則是通過AutowiredAnnotationBeanPostProcessor這個(gè)Bean后置處理器進(jìn)行處理的溉瓶。
所以直接定位到AutowiredAnnotationBeanPostProcessorpostProcessPropertyValues方法急鳄,終于找到注入inject相關(guān)的痕跡啦!

metadata.inject(bean, beanName, pvs);

跳進(jìn)InjectionMetadata繼續(xù)跟進(jìn)inject方法堰酿。

element.inject(target, beanName, pvs);

又跳回到了AutowiredAnnotationBeanPostProcessor疾宏,里面有兩個(gè)內(nèi)部類AutowiredFieldElementAutowiredMethodElement都實(shí)現(xiàn)了inject注入方法。根據(jù)名字很容易想到触创,一個(gè)是Field的注入坎藐,一個(gè)是Method的注入。
兩個(gè)注入都實(shí)現(xiàn)了DefaultListableBeanFactoryresolveDependency解析依賴方法哼绑。

Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);

終于到了本文最關(guān)鍵的位置啦Q意伞!抖韩!睜大眼睛V鳌!茂浮!

resolveDependency方法中真正干活的是doResolveDependency方法双谆。

result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);

注意了!里面有一段調(diào)用resolveMultipleBeans方法励稳,如果解析到了多個(gè)匹配條件的Bean佃乘,就直接返回解析結(jié)果囱井。

Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
    return multipleBeans;
}

解析結(jié)果又是什么呢驹尼?我們進(jìn)去看看代碼咋說的,也到了此次解剖的最后一步庞呕。

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {

    Class<?> type = descriptor.getDependencyType();
    if (type.isArray()) {
        Class<?> componentType = type.getComponentType();
        ResolvableType resolvableType = descriptor.getResolvableType();
        Class<?> resolvedArrayType = resolvableType.resolve();
        if (resolvedArrayType != null && resolvedArrayType != type) {
            type = resolvedArrayType;
            componentType = resolvableType.getComponentType().resolve();
        }
        if (componentType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (getDependencyComparator() != null && result instanceof Object[]) {
            Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
        }
        return result;
    }
    else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
        Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
        if (elementType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        Object result = converter.convertIfNecessary(matchingBeans.values(), type);
        if (getDependencyComparator() != null && result instanceof List) {
            ((List<?>) result).sort(adaptDependencyComparator(matchingBeans));
        }
        return result;
    }
    else if (Map.class == type) {
        ResolvableType mapType = descriptor.getResolvableType().asMap();
        Class<?> keyType = mapType.resolveGeneric(0);
        if (String.class != keyType) {
            return null;
        }
        Class<?> valueType = mapType.resolveGeneric(1);
        if (valueType == null) {
            return null;
        }
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
                new MultiElementDescriptor(descriptor));
        if (matchingBeans.isEmpty()) {
            return null;
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.addAll(matchingBeans.keySet());
        }
        return matchingBeans;
    }
    else {
        return null;
    }
}

由上面代碼得出結(jié)論:

  • 首先判斷注入的類型新翎,如果是數(shù)組、Collection住练、Map地啰,則注入的是元素?cái)?shù)據(jù),即查找與元素類型相同的Bean讲逛,注入到集合中亏吝。
  • 強(qiáng)調(diào)下Map類型,Map的 key 為Bean的 name盏混,value與定義的元素類型相同的Bean蔚鸥。

到此惜论,真相大白了!V古纭馆类!


總結(jié)

/**
 * '將所有類型為BasePrintService的Bean注入為一個(gè)List
 */
@Autowired
private List<BasePrintService> printServiceList;
/**
 * '將所有類型為BasePrintService的Bean注入為一個(gè)Map
 * key為Bean的name
 */
@Autowired
private Map<String, BasePrintService> printServiceMap;

如上述代碼所示,Spring本身就支持弹谁,通過使用@Autowired注解乾巧,將所有相同類型(實(shí)現(xiàn)了同一個(gè)接口)的Bean,一次性注入到集合類型中预愤。
有理有據(jù)沟于,請(qǐng)大家 放心 使用~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市植康,隨后出現(xiàn)的幾起案子社裆,更是在濱河造成了極大的恐慌,老刑警劉巖向图,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泳秀,死亡現(xiàn)場離奇詭異,居然都是意外死亡榄攀,警方通過查閱死者的電腦和手機(jī)嗜傅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來檩赢,“玉大人吕嘀,你說我怎么就攤上這事≌曷鳎” “怎么了偶房?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長军浆。 經(jīng)常有香客問我棕洋,道長,這世上最難降的妖魔是什么乒融? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任掰盘,我火速辦了婚禮,結(jié)果婚禮上赞季,老公的妹妹穿的比我還像新娘愧捕。我一直安慰自己,他們只是感情好申钩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布次绘。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邮偎。 梳的紋絲不亂的頭發(fā)上罗洗,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音钢猛,去河邊找鬼伙菜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛命迈,可吹牛的內(nèi)容都是我干的贩绕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼壶愤,長吁一口氣:“原來是場噩夢啊……” “哼淑倾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起征椒,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤娇哆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勃救,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碍讨,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年蒙秒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勃黍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晕讲,死狀恐怖覆获,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓢省,我是刑警寧澤弄息,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站勤婚,受9級(jí)特大地震影響摹量,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛔六,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一荆永、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧国章,春花似錦、人聲如沸豆村。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至四啰,卻和暖如春宁玫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柑晒。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工欧瘪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匙赞。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓佛掖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涌庭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芥被,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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