手寫簡易SpringMVC框架(二):bean實(shí)例化穷遂、MappingHandler

?? 第二部分緊接第一部分。我們在第一部分中講到了這個(gè)mini框架的幾個(gè)注解毅厚,嵌入的tomcat服務(wù)器塞颁,以及執(zhí)行類掃描和DispatcherSevlet。需要的話可以根據(jù)下面的鏈接回看一下~

友情鏈接
手寫簡易SpringMVC框架(一):注解吸耿、內(nèi)嵌Tomcat祠锣、類掃描
手寫簡易SpringMVC框架(三):極其簡單的應(yīng)用

1.6 BeanFactory實(shí)例化bean并管理

public class BeanFactory {
    private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();

    public static Object getBean(Class<?> cls) {
        return classToBean.get(cls);
    }

    public static void initBean(List<Class<?>> classList) throws Exception {
        //xxxxxxxxxxxxxxx
    }

    private static boolean notNeedCreateBean(Class<?> aClass) {
        //xxxxxxxxxxxxxxxxx
    }
    private static boolean finishCreateBean(Class<?> aClass, Map<Class<?>, Object> notCreateFinishedBeanMap)
            throws IllegalAccessException, InstantiationException {
       //xxxxxxxxxxxxxxx
    }
}

??classToBean記錄了每個(gè)類型class及其對(duì)應(yīng)的實(shí)例對(duì)象,相當(dāng)于容器咽安。getBean方法會(huì)返回對(duì)應(yīng)的bean實(shí)例伴网。initBean方法(誒?好像在哪見過妆棒?澡腾?對(duì)了,是在手寫簡易SpringMVC框架(一)中1.2節(jié)的第3個(gè)步驟中出現(xiàn)過糕珊,有印象了嗎)負(fù)責(zé)實(shí)例化bean并將其放入classToBean容器中动分。我們重點(diǎn)來看一下initBean(List<Class<?>> classList)方法。

?? initBean方法中需要進(jìn)行bean的實(shí)例化红选,1 當(dāng)實(shí)例化一個(gè)bean的時(shí)候澜公,需要首先實(shí)例化它所依賴的bean,例如A中有個(gè)字段指向B實(shí)例喇肋,那么應(yīng)當(dāng)首先實(shí)例化B坟乾,然后在實(shí)例化A迹辐。2 另外還需要考慮到循環(huán)依賴的問題,簡單說就是A依賴于B但是B有依賴于A甚侣,結(jié)果這就導(dǎo)致沒法實(shí)例化這倆實(shí)例明吩。結(jié)合這兩點(diǎn),我們的程序如下(代碼中做了注釋殷费,可以直接看注釋印荔,仔細(xì)體味一下這段代碼):

public static void initBean(List<Class<?>> classList) throws Exception {
        //toCreate存放了所有類的類型
        List<Class<?>> toCreate = new ArrayList<>(classList);
        //用于存放那些還沒創(chuàng)建成功的bean,比如按照順序先要實(shí)例化A宗兼,但是A依賴B躏鱼,B卻沒有被實(shí)例化,那么A
        //自然就沒法實(shí)例化殷绍,所以會(huì)把A對(duì)象暫時(shí)存放在這里染苛。 class<A> --> 未創(chuàng)建完成的A對(duì)象
        Map<Class<?>, Object> notCreateFinishedBeanMap = new HashMap<>();
        while (toCreate.size() != 0) {
            //在這一輪創(chuàng)建之前,先記錄列表里面還有多少個(gè)類
            int size = toCreate.size();

            //下面就是遍歷toCreate中的每一個(gè)class主到,如果它不需要實(shí)例化或者能對(duì)其實(shí)例化成功就將其移除出隊(duì)列茶行,
            //不行的話就保留并添加到notCreateFinishedBeanMap中。
            //注意這里涉及到刪除操作登钥,所以就不能簡單的使用for循環(huán)了畔师。
            int i = 0;
            while (toCreate.size() > i) {
                if (notNeedCreateBean(toCreate.get(i)) || finishCreateBean(toCreate.get(i), notCreateFinishedBeanMap)) {
                    toCreate.remove(i);
                } else {
                    // 這個(gè)i記錄了,沒有被實(shí)例化的class的數(shù)量
                    i++;
                }
            }
            //這一輪創(chuàng)建完成后牧牢,查看是否列表中的元素有減少看锉,減少了說明這一輪有bean創(chuàng)建成功,
            //沒有減少說明沒有bean可以被成功創(chuàng)建 (也就是出現(xiàn)了cycle dependency)
            if (toCreate.size() == size) {
                throw new Exception("cycle dependency");
            }
        }
    }

?? 上面這段代碼的最終結(jié)束循環(huán)的條件toCreate隊(duì)列為空塔鳍,也就是不需要實(shí)例化的對(duì)象移除出了隊(duì)列伯铣,然后需要實(shí)例化的對(duì)象全部實(shí)例化完成。

?? notNeedCreateBean方法十分簡單:class沒有注解的話轮纫,我們就不需要實(shí)例化這些對(duì)象

private static boolean notNeedCreateBean(Class<?> aClass) {
        return !(aClass.isAnnotationPresent(MyBean.class) || aClass.isAnnotationPresent(MyController.class));
    }

?? finishCreateBean方法的主要思路是:如果能夠?qū)嵗?dāng)前對(duì)象腔寡,則完成實(shí)例化并添加到classToBean容器中,返回true掌唾。 如果當(dāng)前類實(shí)例依賴于其他對(duì)象但是此對(duì)象還沒有出現(xiàn)在容器中放前,那么當(dāng)前類實(shí)例添加到notCreateFinishedBeanMap,返回false糯彬,等待后續(xù)創(chuàng)建完成凭语。

private static boolean finishCreateBean(Class<?> aClass, Map<Class<?>, Object> notCreateFinishedBeanMap)
            throws IllegalAccessException, InstantiationException {
        //首先檢查一下這個(gè)類的對(duì)象是否出存在于notCreateFinishedBeanMap中
        Object instance = notCreateFinishedBeanMap.get(aClass);
        if (instance == null) {
            instance = aClass.newInstance();
        }
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(MyAutoWired.class)) {
                Object bean = BeanFactory.getBean(field.getType());
                //判斷容器中是否已有它所依賴的實(shí)例
                if (bean == null) {
                    //there not exist the dependent bean in bean factory, then we can not create bean for this aClass
                    //put the not completely-created bean(instance) into notCreateFinishedBeanMap
                    notCreateFinishedBeanMap.put(aClass, instance);
                    //只要出現(xiàn)依賴的bean還未存在,那么就直接返回false
                    return false;
                }
                //如果已經(jīng)存在它所依賴的bean撩扒,那么就注入它
                field.setAccessible(true);
                field.set(instance, bean);

            }
        }
        //將這個(gè)類實(shí)例化成功的實(shí)例對(duì)象放入容器
        classToBean.put(aClass, instance);
        return true;
    }

1.7 MappingHandler和MappingHandlerManager

?? 至此我們的程序還差一部分似扔,那就是目前系統(tǒng)還不知道哪個(gè)請(qǐng)求uri應(yīng)該映射到哪個(gè)方法中去執(zhí)行呢。還記得1.2中提到的springmvc的入口類的執(zhí)行流程吧,這個(gè)十分重要虫几,記得回看一下。在第4步:

MyMappingHandlerManager.resolveMappingHandler(classList);

這里面就建立了請(qǐng)求uri和對(duì)應(yīng)的方法之間的映射關(guān)系挽拔。我們先點(diǎn)進(jìn)MyMappingHandlerManager看一看辆脸。

public class MyMappingHandlerManager {
    /**
     * MappingHandler列表,一個(gè)MappingHandler可以認(rèn)為是一對(duì) 請(qǐng)求uri-->執(zhí)行方法 的封裝
     */
    public static List<MyMappingHandler> mappingHandlerList = new ArrayList<>();

    /**
     * 在解析mapping的時(shí)候螃诅,只會(huì)講controller進(jìn)行解析
     * @param classList 是類掃描時(shí)掃描到的所有的類啡氢。
     */
    public static void resolveMappingHandler(List<Class<?>> classList) {
        for (Class<?> aClass : classList) {
            if (aClass.isAnnotationPresent(MyController.class)) {
                parseMappingHandlerFromController(aClass);
            }
        }
    }

    /**
     *
     * @param controllerClass
     */
    private static void parseMappingHandlerFromController(Class<?> controllerClass) {
        // get all methods
        Method[] methods = controllerClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyRequestMapping.class)) {
                //獲取到當(dāng)前方法上@MyRequestMapping注解中的請(qǐng)求uri
                String mappingUri = method.getDeclaredAnnotation(MyRequestMapping.class).value();
                List<String> requestParamList = new LinkedList<>();
                for (Parameter parameter : method.getParameters()) {
                    if (parameter.isAnnotationPresent(MyRequestParam.class)) {
                        //獲取到當(dāng)前方法中@MyRequestParam所需要的請(qǐng)求參數(shù)的名字,并將其添加到當(dāng)前方法的請(qǐng)求參數(shù)列表中
                        requestParamList.add(parameter.getDeclaredAnnotation(MyRequestParam.class).value());
                    } else {
                        //如果沒有用@MyRequestParam注解术裸,那使用方法參數(shù)的名字
                        requestParamList.add(parameter.getName());
                    }
                }
                String[] requestParams = requestParamList.toArray(new String[requestParamList.size()]);
                //對(duì)每一個(gè)uri和method的映射封裝一個(gè)MyMappingHandler對(duì)象(其中還包括了方法所述的controller以及方法需要的參數(shù)名列表)倘是,
                // 并放入mappingHandlerList中。
                MyMappingHandler mappingHandler = new MyMappingHandler(mappingUri, method, controllerClass, requestParams);
                mappingHandlerList.add(mappingHandler);
            }
        }
    }
}

?? 好的袭艺,現(xiàn)在映射關(guān)系也已經(jīng)建立好了搀崭,但是還差一丟丟,大家還記得當(dāng)時(shí)講MyDispatcherServlet的時(shí)候猾编,我提到了一句瘤睹,它只負(fù)責(zé)分發(fā)請(qǐng)求,不負(fù)責(zé)具體執(zhí)行的答倡,那么到底是誰在執(zhí)行請(qǐng)求呢轰传,

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI();

        for (MyMappingHandler mappingHandler : MyMappingHandlerManager.mappingHandlerList) {
            if(requestURI.equals(mappingHandler.getUri())){
                try {
                    mappingHandler.handle(req, resp);
                } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

?? 沒錯(cuò),就是這個(gè)MyMappingHandler這個(gè)玩意兒瘪撇。也就是說它除了負(fù)責(zé)建立uri和method的映射(mapping)之外获茬,還負(fù)責(zé)執(zhí)行請(qǐng)求(handler),大概這也就是為啥給他起了名字叫MappingHandler吧~(我猜的)倔既。那我們就不得不來看一看這個(gè)MyMappingHandler了恕曲。

public class MyMappingHandler {
    private String uri;
    private Method method;
    private Class<?> controllerClass;
    private String[] requestParams;

    public MyMappingHandler(String uri, Method method, Class<?> controllerClass, String[] requestParams) {
        this.uri = uri;
        this.method = method;
        this.controllerClass = controllerClass;
        this.requestParams = requestParams;
    }

    public String getUri() {
        return uri;
    }

    public Method getMethod() {
        return method;
    }

    public Class<?> getControllerClass() {
        return controllerClass;
    }

    public String[] getRequestParams() {
        return requestParams;
    }

    public void handle(HttpServletRequest req, HttpServletResponse resp)
            throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        String[] args = new String[this.requestParams.length];
        for (int i = 0; i < args.length; i++) {
            //根據(jù)方法需要的參數(shù)的名稱去從request中獲取對(duì)應(yīng)的參數(shù)值
            args[i] = req.getParameter(requestParams[i]);
        }
        Object controller = BeanFactory.getBean(controllerClass);
        //反射的方式執(zhí)行方法時(shí)需要方法所處的對(duì)象實(shí)例,也就是我們已經(jīng)創(chuàng)建好的Controller bean
        Object result = this.method.invoke(controller, (Object[]) args);
        String resultString = result.toString();
        //將結(jié)果返回叉存。這里返回的方式也很簡單码俩,直接寫入字符串
        resp.getWriter().print(resultString);
    }
}

2 梳理一下這個(gè)簡單框架的執(zhí)行流程

??讓我們再來看一下MVCApplication這個(gè)入口類的執(zhí)行流程,唉歼捏,我都已經(jīng)不知道多少次提起它了稿存,應(yīng)該就結(jié)束了了吧~

public class MVCApplication {
    public static void run(Class<?> cls) {
        System.out.println("=============================start!!!!==========================");

        TomcatServer tomcatServer = new TomcatServer();
        try {
            tomcatServer.startServer();

            List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
            BeanFactory.initBean(classList);
            MyMappingHandlerManager.resolveMappingHandler(classList);

            for (MyMappingHandler myMappingHandler : MyMappingHandlerManager.mappingHandlerList) {
                System.out.println(myMappingHandler.getUri() + ":" + myMappingHandler.getMethod());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

?? 1首先就是要配置(端口,主機(jī)名瞳秽,注冊servlet等)并啟動(dòng)tomcat瓣履;
?? 2掃描用戶的應(yīng)用程序中的類
?? 3將需要實(shí)例化的bean實(shí)例化,其中解決了bean的依賴以及注意循環(huán)依賴問題练俐。
?? 4MappingHandlerManager解析請(qǐng)求和method之間的映射袖迎,同時(shí)解析出每個(gè)方法需要哪些參數(shù)名稱,MappingHandler對(duì)象還具有執(zhí)行對(duì)應(yīng)的請(qǐng)求的邏輯。

這樣整個(gè)系統(tǒng)就啟動(dòng)初始化并配置完成了燕锥,下面的第三部分辜贵,將結(jié)合一個(gè)十分簡單的應(yīng)用來驗(yàn)證一下這個(gè)微小框架的正確性。

最后依然甩一下鏈接:
·手寫簡易SpringMVC框架(一):注解归形、內(nèi)嵌Tomcat托慨、類掃描
·手寫簡易SpringMVC框架(三):極其簡單的應(yīng)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市暇榴,隨后出現(xiàn)的幾起案子厚棵,更是在濱河造成了極大的恐慌,老刑警劉巖蔼紧,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婆硬,死亡現(xiàn)場離奇詭異,居然都是意外死亡奸例,警方通過查閱死者的電腦和手機(jī)彬犯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來查吊,“玉大人躏嚎,你說我怎么就攤上這事∑忻玻” “怎么了卢佣?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長箭阶。 經(jīng)常有香客問我虚茶,道長,這世上最難降的妖魔是什么仇参? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任嘹叫,我火速辦了婚禮,結(jié)果婚禮上诈乒,老公的妹妹穿的比我還像新娘罩扇。我一直安慰自己,他們只是感情好怕磨,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布喂饥。 她就那樣靜靜地躺著,像睡著了一般肠鲫。 火紅的嫁衣襯著肌膚如雪员帮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天导饲,我揣著相機(jī)與錄音捞高,去河邊找鬼氯材。 笑死,一個(gè)胖子當(dāng)著我的面吹牛硝岗,可吹牛的內(nèi)容都是我干的氢哮。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼型檀,長吁一口氣:“原來是場噩夢啊……” “哼命浴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贱除,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媳溺,沒想到半個(gè)月后月幌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悬蔽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年扯躺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝎困。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡录语,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出禾乘,到底是詐尸還是另有隱情澎埠,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布始藕,位于F島的核電站蒲稳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏伍派。R本人自食惡果不足惜江耀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诉植。 院中可真熱鬧祥国,春花似錦、人聲如沸晾腔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灼擂。三九已至扩借,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缤至,已是汗流浹背潮罪。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國打工康谆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嫉到。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓沃暗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親何恶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子孽锥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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