造一個(gè)方形的輪子4--依賴注入

造一個(gè)方形輪子文章目錄:造一個(gè)方形的輪子

01澄干、先把車正過(guò)來(lái)

在上一篇《造一個(gè)方形的輪子3--控制反轉(zhuǎn)》的最后提出了一個(gè)問(wèn)題,如果同一個(gè)接口有一個(gè)以上的實(shí)現(xiàn)類,那么在初始化的時(shí)候肯夏,實(shí)現(xiàn)相同接口的BeanObject對(duì)象,后一個(gè)放入Map容器中時(shí)會(huì)把前邊的覆蓋掉桥状,這樣肯定有問(wèn)題,簡(jiǎn)單處理一下硝清,在BeanObject類中添加一個(gè)next指針引用辅斟,把他改造成一個(gè)可以支持鏈表的形式。

BeanObject.java 添加:

    //......上略
    /**
     * 使用鏈表處理同一接口多個(gè)實(shí)現(xiàn)類的情況
     */
    private BeanObject next;

    public BeanObject getNext() {
        return next;
    }

    public void setNext(BeanObject next) {
        this.next = next;
    }
    //......下略

在原來(lái)的BeansInitUtil.loadClass()方法里修改以下代碼:

                //......上略
                beanObject.setPackages(clzz.getPackage().toString());
                Object obj = clzz.newInstance();
                beanObject.setObject(obj);
                // 按接口設(shè)置bean
                for (Class aClass : beanObject.getInterfacs()) {
                    BeanObject tmp = map.get(aClass.getName());
                    if(tmp != null){
                        beanObject.setNext(tmp);
                    }
                    map.put(aClass.getName(), beanObject);
                }
                // 按類設(shè)置bean
                map.put(beanObject.getClassName(), beanObject);
                //......下略


可以看到在按接口全限定名保存容器時(shí)芦拿,由原來(lái)的直接保存改為先查詢士飒,如果原來(lái)有值(tmp不為空)說(shuō)明已有其它實(shí)現(xiàn)類,被初始化蔗崎,那么將當(dāng)然beanObject的next指針指向原來(lái)保存的值就可以了(為什么要將當(dāng)前beanObject的next指針指向已存在的tmp對(duì)象酵幕,而不是將tmp的next指針指向beanObject? )。

02缓苛、依賴注入準(zhǔn)備

到目前為止bean的實(shí)例化已經(jīng)完成了芳撒,現(xiàn)在把依賴注入的過(guò)程大概理一下:

1、找到每個(gè)bean中添加的注入注解的字段

2、按類型笔刹、接口芥备、名稱去容器中查找Bean實(shí)例

3、如果找到了且為一舌菜,則使用反射設(shè)值

4萌壳、如果沒(méi)有找到或者找到多個(gè)沒(méi)法確定使用哪一個(gè),則拋出異常(啟動(dòng)失斎赵隆)

好袱瓮,先來(lái)實(shí)現(xiàn)第一步,改造一下BeanObject 把字段也保存起來(lái)爱咬,好在DI階段使用尺借,注解的這里就不自己實(shí)現(xiàn)了直接使用Java提供的@Resource, @Resource注解有一個(gè)name的字段,我們把他定義為指定的bean名字台颠。

BeanObject.java 添加字段屬性:

    //.....
    /**
     * 字段數(shù)組
     */
    private Field[] fields;
    public Field[] getFields() {
        return fields;
    }
    public void setFields(Field[] fields) {
        this.fields = fields;
    }

03、處理依賴注入

在BeansInitUtil.java中添加initDI()方法負(fù)責(zé)處理依賴注入:

    //....上略
    /**
     * 處理關(guān)系依賴
     * @param map Bean容器
     */
    private static void initDI(Map<String, BeanObject> map){
        // 循環(huán)所有Bean處理依賴
        for(Map.Entry entry : map.entrySet()){
            BeanObject beanObject = (BeanObject)entry.getValue();
            // 先判斷是否有Resource注解
            for (Field field : beanObject.getFields()) {
                if(filterFieldAnnotation(field.getAnnotations())){
                    String name = getResourceName(field.getAnnotations());
                    BeanObject bean = null;
                    // 有指定bean名字按指定去取
                    if(name != null && !name.equals("")){
                        bean = map.get(firstToLowerCase(name));
                    } else {
                        // 沒(méi)有指定按接口(如果有的話)或類型去取
                        Class fieldClass = field.getType();
                        bean = map.get(fieldClass.getName());
                        // 如果有next說(shuō)明是有多個(gè)實(shí)現(xiàn)的接口,則要判斷名字
                        if(bean != null && bean.getNext() != null){
                            String fieldName = field.getName();
                            while(bean != null){
                                if(firstToLowerCase(bean.getSimpleName()).equals(fieldName)){
                                    break;
                                }
                                bean = bean.getNext();
                            }
                            if(bean == null){
                                // 多于兩個(gè)匹配的bean異常
                                log.error("無(wú)法確定的Bean依賴谨读,field:{}, 存在多個(gè)依賴劳殖!", beanObject.getClassName()+"."+fieldName);
                                throw new SquareBeanInitException("無(wú)法確定的Bean依賴哆姻,存在多個(gè)依賴矛缨!");
                            }
                        }
                    }
                    if(bean == null){
                        // 找不到依賴bean異常
                        log.error("無(wú)法找到Bean依賴帖旨,field:{}", beanObject.getClassName()+"."+field.getName());
                        throw new SquareBeanInitException("無(wú)法找到Bean依賴");
                    }
                    // 注入依賴
                    try {
                        field.setAccessible(true);
                        field.set(beanObject.getObject(), bean.getObject());
                    } catch (IllegalAccessException e) {
                        log.error("Bean注入失敗解阅,field:{}", beanObject.getClassName()+"."+field.getName(), e);
                        throw new SquareBeanInitException("Bean注入失敗");
                    }
                }
            }
        }
    }
    /** * 判斷字段上加的注解是否需要做注入 */
    private static boolean filterFieldAnnotation(Annotation[] annotations){
        boolean b = false;
        for (Annotation annotation : annotations) {
            b = annotation instanceof Resource;
        }
        return b;
    }
    /**  獲取注入注解上指定的Bean名字 */
    private static String getResourceName(Annotation[] annotations){
        String name = null;
        for (Annotation annotation : annotations) {
            name = ((Resource)annotation).name();
        }
        return name;
    }

    /** 首字段轉(zhuǎn)大寫(xiě)*/
    public static String firstToUpperCase(String str){
        if(str == null || str.equals("")){
            return str;
        }
        char f = str.charAt(0);
        str = str.substring(1);
        if(f>'Z'){
            f = (char)(f-32);
        }
        return f+str;
    }

    /** 首字段轉(zhuǎn)大寫(xiě)*/
    public static String firstToLowerCase(String str){
        if(str == null || str.equals("")){
            return str;
        }
        char f = str.charAt(0);
        str = str.substring(1);
        if(f<'a'){
            f = (char)(f+32);
        }
        return f+str;
    }
    //....下略

看注釋就可以了述召,基本就是按照上邊說(shuō)的邏輯一步一步做的桨武,這里主要的關(guān)注點(diǎn)在呀酸,同一個(gè)接口有多個(gè)實(shí)現(xiàn)的時(shí)候琼梆,怎么去確認(rèn)要注入的bean茎杂,上邊代碼里使用的是如果有多個(gè)實(shí)例則按需要注入字段的名字去匹配煌往。

在BeansInitUtil.java的init()方法中添加調(diào)用initDI()刽脖。

    //...上略
    public static Map<String, BeanObject> init(Class clazz){
        Map<String, BeanObject> beansMap = new HashMap<>();
        String path = clazz.getResource("").getPath();
        log.info("===bean init path:{}", path);
        File root = new File(path);
        // 處理控制反轉(zhuǎn)
        initFile(root, beansMap);
        // 處理依賴注入
        initDI(beansMap);
        return beansMap;
    }
    //...下略

04曲管、驗(yàn)證依賴注入

為了驗(yàn)證效果我們先在com.jisuye.service包下添加幾個(gè)類

ClassDI.java

package com.jisuye.service;
import com.jisuye.annotations.Service;
@Service
public class ClassDi {
    public String exe(String name){
        return "Class DI "+name;
    }
}

Def.java

package com.jisuye.service;
public interface Def {
    String exe(String name);
}

添加兩個(gè)Def接口實(shí)現(xiàn)類

DefImpl.java

package com.jisuye.service.impl;
import com.jisuye.annotations.Service;
import com.jisuye.service.Def;
@Service
public class DefImpl implements Def {
    @Override
    public String exe(String name) {
        return "Interface DI... "+name;
    }
}

Def2Impl.java

package com.jisuye.service.impl;
import com.jisuye.annotations.Service;
import com.jisuye.service.Def;
@Service
public class Def2Impl implements Def {
    @Override
    public String exe(String name) {
        return "def2 "+name;
    }
}

修改AbcImpl.java(上一篇?jiǎng)?chuàng)建的類) 添加需要注入的引入

package com.jisuye.service.impl;
//import ...
@Service
public class AbcImpl implements Abc {
    // 名字對(duì)不上會(huì)報(bào)異常
    @Resource
    private Def defImpl;
    // 名字對(duì)不上可以使用注解中指定bean名字的方式
    @Resource(name = "def2Impl")
    private Def defByName;
    // 注入Class類實(shí)例
    @Resource
    private ClassDi classDi;
    @Override
    public int test(String name) {
        System.out.println(defImpl.exe(name));
        System.out.println(defByName.exe(name));
        System.out.println(classDi.exe(name));
        return 0;
    }
}

在SquareApplication.run()方法中添加調(diào)用查看注入是否成功

 public static void run(Class clzz, String[] args) {
        try {
            long startTime = System.currentTimeMillis();
            classesPathUtil = new ClassesPathUtil(clzz);
            // 加載配置
            loadYaml(classesPathUtil.getProjectPath());
            // 初始化參數(shù)
            setArgs(args);
            // 輸出banner
            printBanner(classesPathUtil.getProjectPath());
            Map<String, BeanObject> map = BeansInitUtil.init(clzz);
            log.info("beans size is:{}", map.size());
            //查看bean是否注入成功
            Abc abc = (Abc)(map.get("com.jisuye.service.Abc").getObject());
            abc.test("ixx");
            tomcat = new Tomcat();
  //....下略

查看控制臺(tái)輸出:

20:42:07.749 [main] INFO com.jisuye.core.SquareApplication - beans size is:10
Interface DI... ixx
def2 ixx
Class DI ixx
....

ok 可以看到對(duì)應(yīng)的屬性都已經(jīng)注入成功

05、翻車時(shí)間

重新思考一下撬腾,上一篇只考慮了同一接口的不同實(shí)現(xiàn)類民傻,會(huì)造成BeanObject覆蓋問(wèn)題饰潜,但其實(shí)按類型簡(jiǎn)稱(不帶包名)以及按注解上設(shè)置的Bean名字去初始化Bean的時(shí)候都會(huì)有覆蓋問(wèn)題彭雾,比如不同包下的相同的類锁保,或者在注解上設(shè)置了相同Bean名字的類,想一下要怎么處理呢者填?

本篇代碼地址: https://github.com/iuv/square/tree/square4

本文作者: ixx
本文鏈接: http://jianpage.com/2019/06/29/square4
版權(quán)聲明: 本作品采用 知識(shí)共享署名-非商業(yè)性使用-相同方式共享 4.0 國(guó)際許可協(xié)議 進(jìn)行許可做葵。轉(zhuǎn)載請(qǐng)注明出處酿矢!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘫筐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌之众,老刑警劉巖酝枢,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帘睦,死亡現(xiàn)場(chǎng)離奇詭異竣付,居然都是意外死亡古胆,警方通過(guò)查閱死者的電腦和手機(jī)逸绎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)棺牧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颊乘,“玉大人醉锄,你說(shuō)我怎么就攤上這事恳不⊙萄” “怎么了负蚊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)伤极。 經(jīng)常有香客問(wèn)我哨坪,道長(zhǎng)当编,這世上最難降的妖魔是什么徒溪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任鲤桥,我火速辦了婚禮茶凳,結(jié)果婚禮上贮喧,老公的妹妹穿的比我還像新娘箱沦。我一直安慰自己饱普,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布冯袍。 她就那樣靜靜地躺著康愤,像睡著了一般征冷。 火紅的嫁衣襯著肌膚如雪检激。 梳的紋絲不亂的頭發(fā)上叔收,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天窃页,我揣著相機(jī)與錄音脖卖,去河邊找鬼。 笑死馋劈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的械姻。 我是一名探鬼主播楷拳,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼她混,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坤按!你這毒婦竟也來(lái)了臭脓?” 一聲冷哼從身側(cè)響起算吩,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兼耀,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钱磅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年梦裂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盖淡。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡年柠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出褪迟,到底是詐尸還是另有隱情冗恨,我是刑警寧澤答憔,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站掀抹,受9級(jí)特大地震影響虐拓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渴丸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一侯嘀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谱轨,春花似錦戒幔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至献汗,卻和暖如春敢订,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背罢吃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工楚午, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尿招。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓矾柜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親就谜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怪蔑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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