跟我動(dòng)手搭框架三之Web容器實(shí)現(xiàn)

本篇主要對Web的實(shí)現(xiàn)做說明,在參考文章的同事,可以把code clone下來,看,代碼中有很多需要優(yōu)化的地址,我已經(jīng)用TODO標(biāo)記處理啊,小編會(huì)不斷的進(jìn)行優(yōu)化和分析,演示SmileBootDemo也可以git clone,debug學(xué)習(xí)

Smile源碼地址

SmileBootDemo

目錄

    1. 核心描述類介紹
    1. Smile啟動(dòng)核心實(shí)現(xiàn)
    1. Http請求多線程異步實(shí)現(xiàn)
    1. 下一篇主要介紹內(nèi)容
    1. 擴(kuò)展

    ?

1. 核心描述類,主要保存處理方法及參數(shù)類型

其實(shí)所有方法的執(zhí)行,都離不開ioc的實(shí)現(xiàn),IOC主要將組件(被@SmileComponent標(biāo)記過的都為組件)保存為BeanDefinition的形式,而組件中的method,主要保存為WebDefinition的形式,而method的參數(shù)名稱,參數(shù)類型,參數(shù)位置索引,主要保存在ParamterDefinition,對于請求的處理,就是根據(jù)url找到對應(yīng)的method,然后根據(jù)ParamterDefinition將請求參數(shù),轉(zhuǎn)換成參數(shù)原本類型,然后處理

描述類 說明 存放位置
BeanDefinition 保存組件Class字節(jié)碼及實(shí)例化對象 Map<String(beanName), BeanDefinition> registeredBeans
WebDefinition 保存Url及對應(yīng)的處理方法,及實(shí)例化對象,及參數(shù)類型 Map<String(url), WebDefinition> webHandlerMaps = new ConcurrentHashMap<>();
ParamterDefinition 保存參數(shù)位置索引及參數(shù)名稱,參數(shù)類型,參數(shù)注解 WebDefinition.ParamterDefinition

2. Smile啟動(dòng)核心

  • 2.1 SmileApplicationContext 掃描所有組件,并check 是否需要代理,最終生成IOC容器

    IOC實(shí)現(xiàn)

    Map<String, BeanDefinition> registeredBeans = new ConcurrentHashMap<>();

  • 2.2 SmileApplicationContext生成IOC容器之后,加載是否有實(shí)現(xiàn)ExtApplicationContext擴(kuò)展類scanExtContext()方法

    WebApplicationContext此時(shí)會(huì)執(zhí)行,并掃描被@ResController注解修飾的路由類,然后

    掃描其Methods,獲取方法中有@GetMapping和@PostMapping 的方法生成WebDefinitionParamterDefinition

    Consumer<Map.Entry<String, BeanDefinition>> entryConsumer = entry -> {
                BeanDefinition beanDefinition = entry.getValue();
                Class<?> controllerClass = beanDefinition.getClazz();
                RestController annotation = controllerClass.getAnnotation(RestController.class);
                String oneUrl = annotation.value();
                Method[] methods = controllerClass.getMethods();
                for (Method method : methods) {
                    boolean isGet = method.isAnnotationPresent(GetMapping.class);
                    if (isGet) {
                        bindGetMethod(oneUrl, method, beanDefinition);
                    }
                    boolean isPost = method.isAnnotationPresent(PostMapping.class);
                    if (isPost) {
                        bindPostMethod(oneUrl, method, beanDefinition);
                    }
                }
            };
            definitionMap.entrySet().forEach(entryConsumer);
    
    /**
         * 綁定get請求
         *
         * @param oneUrl         一級url
         * @param method         方法
         * @param beanDefinition bean描述
         */
        public void bindGetMethod(String oneUrl, Method method, BeanDefinition beanDefinition) {
            Object controllerInstance = beanDefinition.getInstance();
            Package aPackage = beanDefinition.getClazz().getPackage();
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            String twoUrl = getMapping.value();
            String[] parameterNames = WebTools.getParameterNames(method);
            if (StringTools.isEmpty(twoUrl)) {
                throw new BindUrlHanderException("[ " + aPackage.getName() + " ]:綁定url異常,請檢查,請?zhí)顚懶枰壎ǖ膗rl地址");
            }
            String realUrl = WebTools.checkUrl(oneUrl, twoUrl);
            String methodPath = method.toGenericString();
            logger.info("Mapped url:[{}],produces:[{}],consumes:[{}],paramter:{},onto:{}", realUrl, getMapping.produces(), getMapping.consumes(), parameterNames, methodPath);
            webHandlerMaps.put(realUrl, new WebDefinition(realUrl, RequestMethod.GET, getMapping.consumes(), getMapping.produces(), controllerInstance, method, parameterNames));
        }
    

    ?

3. HTTP處理實(shí)現(xiàn)核心

  • 3.1將每個(gè)請求生成一個(gè)MessageRequest及MessageResponse,作為一個(gè)任務(wù)交給線程池去異步執(zhí)行

    MessageRequest messageRequest = new MessageRequest(randomUUID, requestMethod, requestParams, webDefinition, headerMaps);
    MessageResponse messageResponse = new MessageResponse();
    SmileTaskChoice smileTaskChoice = new DefaultTaskProcessChoice(messageRequest, messageResponse, false);
    SmileMessageExecutor.submit(smileTaskChoice.choice(), ctx, req, messageRequest, messageResponse);
    
  • 3.2 SmileTaskChoice 是執(zhí)行策略,當(dāng)為ture時(shí)候,為rpc默認(rèn),可以定義自己的rpc遠(yuǎn)程調(diào)用的方式實(shí)現(xiàn)結(jié)果返回

    public class DefaultTaskProcessChoice implements SmileTaskChoice {
        private boolean isRpc = false;
        private MessageRequest messageRequest;
        private MessageResponse messageResponse;
    
        /**
         *
         * @param request
         * @param response
         * @param isRpc 本地方法:false rpc調(diào)用:true
         */
        public DefaultTaskProcessChoice(final MessageRequest request, final MessageResponse response, boolean isRpc) {
            this.messageRequest = request;
            this.messageResponse = response;
            this.isRpc = isRpc;
        }
    
        @Override
        public Callable choice() {
            Callable callTask =null;
            if (!isRpc) {
                callTask = new LocalMessageTask(messageRequest, messageResponse);
            }else {
                callTask=new RpcProcessTask(messageRequest,messageResponse);
            }
            return callTask;
        }
    }
    

    ?

  • 3.3 SmileTask中

主要利用反射魔熏,將請求 URL 獲取到 WebDefinition ,拿到執(zhí)行方法,將請求參數(shù),綁定到方法,作為實(shí)參,傳遞

Object invokeResult = method.invoke(controller, args);

并通過Netty 連接通道Channel把處理結(jié)果返回給客戶端

  • 3.4 SmileMessageExecutor.submit 方法中監(jiān)聽任務(wù)是否成功處理,成功并通過Netty 連接通道Channel把處理結(jié)果返回給客戶端

     public static void submit(Callable<Boolean> task, final ChannelHandlerContext ctx, HttpRequest metaRequest, final MessageRequest request, final MessageResponse response) {
    
            /**
             * SmileThreadFactory 目的構(gòu)建自己的線程名,并通過線程組進(jìn)行統(tǒng)一管理
             * SmileThreadPoolExecutor 構(gòu)建自己的線程池,對任務(wù)進(jìn)行,細(xì)微管理
             */
            if (threadPoolExecutor == null) {
                SmileThreadPoolExecutor smileThreadPoolExecutor = new SmileThreadPoolExecutor(new SmileThreadFactory("Smile"));
                ThreadPoolExecutor executorService = (ThreadPoolExecutor) smileThreadPoolExecutor.getExecutory();
                threadPoolExecutor = MoreExecutors.listeningDecorator(executorService);
            }
            /**
             * 處理完成任務(wù)如果任務(wù)完成就,渲染出去
             */
            ListenableFuture<Boolean> listenableFuture = threadPoolExecutor.submit(task);
            Futures.addCallback(listenableFuture, new FutureCallback<Boolean>() {
                @Override
                public void onSuccess(Boolean result) {
                    if (result){
                        NettyResponse.writeResponseAndListener(ctx.channel(), request, response, new ChannelFutureListener() {
                            @Override
                            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                                channelFuture.channel().close();
                                logger.info("Smile Server Send message-id:{}" , request.getMessageId());
                            }
                        });
                    }
                }
                @Override
                public void onFailure(Throwable t) {
                    t.printStackTrace();
                }
            }, threadPoolExecutor);
    
        }
    

    ?

4. 下一篇主要介紹內(nèi)容

框架的實(shí)現(xiàn)方案屬于傳統(tǒng)的MVC機(jī)構(gòu),只不過吧視圖層V取消掉了,這也是趨勢,前后分離,后端只做關(guān)心數(shù)據(jù)處理,

而傳統(tǒng)的MVC架構(gòu),核心為Servlet,SpringMVC核心為DispatchServlet,是對原始Java Servlet的一個(gè)封裝,關(guān)于這點(diǎn)可以看小編的另一篇文章手寫一個(gè)輕量級的網(wǎng)關(guān)API,當(dāng)然使用Netty也是如此,我們的入口就是HttpDispatchServerHandler 核心方法就是messageReceivedDispatch

而只知道,這些是遠(yuǎn)遠(yuǎn)不夠的,Netty是由JBOSS提供的一個(gè)java開源框架锈颗。Netty提供異步的淳衙、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具,我會(huì)新開二篇,專門介紹IO模型重點(diǎn)介紹IO multiplexing(IO多路復(fù)用)Netty如何工作 ! 包括如何實(shí)現(xiàn),心跳檢測

主要會(huì)介紹下面寫模塊

  • Bootstrap or ServerBootstrap
  • EventLoop
  • EventLoopGroup
  • ChannelPipeline
  • Future or ChannelFuture
  • ChannelInitializer
  • ChannelHandler
  • ByteToMessageDecoder
  • MessageToByteEncoder
 public void messageReceivedDispatch(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        String dispatchUrl = "";
        Map<String, Object> headerMaps = new ConcurrentHashMap<>();
        if (msg instanceof HttpRequest) {
            HttpRequest req = this.request = (HttpRequest) msg;
            HttpHeaders headers = req.headers();
            headers.entries().stream().forEach(x -> {
                headerMaps.put(x.getKey(), x.getValue());
            });
            String contentType = request.headers().get("Content-Type");
            String methodName = request.getMethod().name();
            dispatchUrl = req.getUri();
            String randomUUID = UUID.randomUUID().toString().replaceAll("-", "");
            Map<String, Object> requestParams = new ConcurrentHashMap<>();
            // 處理get請求
            if (methodName.equalsIgnoreCase("GET")) {
                boolean contains = dispatchUrl.contains("?");
                if (contains){
                    String queryContent = dispatchUrl.substring(dispatchUrl.indexOf("?") + 1);
                    Map<String, Object> queryParameterFromContent = URLTools.getQueryParameterFromContent(queryContent);
                    queryParameterFromContent.entrySet().forEach(entry -> {
                        requestParams.put(entry.getKey(), entry.getValue());
                    });
                }
            }
            // 處理POST請求
            if (methodName.equalsIgnoreCase("POST")) {
                if (StringTools.endsWithIgnoreCase(contentType, "application/json")) {
                    FullHttpRequest request1 = (FullHttpRequest) msg;
                    ByteBuf jsonBuf = request1.content();
                    String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8).replaceAll("\\\\s*|\\t|\\r|\\n", "");
                    if (!StringTools.isEmpty(jsonStr)) {
                        requestParams.put("BODY", jsonStr);
                    }
                } else {
                    HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(
                            new DefaultHttpDataFactory(false), req);
                    List<InterfaceHttpData> postData = decoder.getBodyHttpDatas(); //
                    for (InterfaceHttpData data : postData) {
                        if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                            MemoryAttribute attribute = (MemoryAttribute) data;
                            requestParams.put(attribute.getName(), attribute.getValue());
                        }
                    }
                }
            }
            if (StringTools.contains(dispatchUrl,"?")){
                dispatchUrl = dispatchUrl.substring(0, dispatchUrl.indexOf("?"));
            }
            RequestMethod requestMethod = WebTools.getRequestMethod(methodName);
            WebDefinition webDefinition = WebContextTools.getWebDefinitionByUrl(dispatchUrl, requestMethod);
            if (webDefinition instanceof Web404Definition) {
                NettyResponse.writeResponse(ctx.channel(), "Not Found", HttpResponseStatus.NOT_FOUND);
                return;
            }
            if (webDefinition instanceof Web405Definition) {
                NettyResponse.writeResponse(ctx.channel(), "Method Not Allowed", HttpResponseStatus.METHOD_NOT_ALLOWED);
                return;
            }
            String consumes = webDefinition.getConsumes();
            if (StringTools.isNotEmpty(contentType)){
                if (StringTools.isNotEmpty(consumes)&(!contentType.equalsIgnoreCase(consumes))){
                    NettyResponse.writeResponse(ctx.channel(), "Bad Request (The content-type don't match)", HttpResponseStatus.BAD_REQUEST);
                    return;
                }
            }
            /**
             * //TODO 異步處理url獲取處理的 bean
             */
            MessageRequest messageRequest = new MessageRequest(randomUUID, requestMethod, requestParams, webDefinition, headerMaps);
            MessageResponse messageResponse = new MessageResponse();
            /**
             * //TODO 根據(jù)啟動(dòng)配置,當(dāng)如果是rpc服務(wù)就要使用MessageProcessTask
             * 如果是本地服務(wù)使用LocalMessageTask
             *
             * 此時(shí)MessageRequest和MessageResponse都是final 修飾,目的是保證始終是對當(dāng)前的MessageResponse
             */
            SmileTaskChoice smileTaskChoice = new DefaultTaskProcessChoice(messageRequest, messageResponse, false);
            /**
             * //TODO 交給線程處理異步處理響應(yīng)
             */
            SmileMessageExecutor.submit(smileTaskChoice.choice(), ctx, req, messageRequest, messageResponse);
        }
    }

擴(kuò)展

再次聲明小編也是一個(gè)菜鳥,是一只具有學(xué)習(xí)精神,并熱愛編程的菜鳥, 所有的文章都是經(jīng)過參考很多優(yōu)秀博文,給我?guī)淼倪M(jìn)步,小編,希望將學(xué)習(xí)到的所有知識(shí)點(diǎn),也分享給大家 ! 小編會(huì)在這里列出,參考到的優(yōu)秀博文,尊重每位知識(shí)傳播者的勞動(dòng)果實(shí).

如果您發(fā)現(xiàn)小編文章中,有錯(cuò)誤,請及時(shí)指出,并通知小編改正,小編在此謝過.

歡迎繼續(xù)關(guān)注小編~ 小編努力coding…

參考

Smart Framework 設(shè)計(jì)動(dòng)力來源

segmentfault-Netty 源碼分析 Netty強(qiáng)化學(xué)習(xí)

SpringIOC源碼分析 描述類靈感來源

基于Netty打造RPC服務(wù)器設(shè)計(jì)經(jīng)驗(yàn)談

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轩缤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子歪架,更是在濱河造成了極大的恐慌妖胀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赎败,死亡現(xiàn)場離奇詭異,居然都是意外死亡蠢甲,警方通過查閱死者的電腦和手機(jī)僵刮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹦牛,“玉大人搞糕,你說我怎么就攤上這事÷罚” “怎么了窍仰?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長礼殊。 經(jīng)常有香客問我驹吮,道長针史,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任碟狞,我火速辦了婚禮啄枕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘篷就。我一直安慰自己射亏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布竭业。 她就那樣靜靜地躺著智润,像睡著了一般。 火紅的嫁衣襯著肌膚如雪未辆。 梳的紋絲不亂的頭發(fā)上窟绷,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音咐柜,去河邊找鬼兼蜈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拙友,可吹牛的內(nèi)容都是我干的为狸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼遗契,長吁一口氣:“原來是場噩夢啊……” “哼辐棒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牍蜂,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤漾根,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鲫竞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辐怕,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年从绘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寄疏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡僵井,死狀恐怖赁还,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驹沿,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布蹈胡,位于F島的核電站渊季,受9級特大地震影響朋蔫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜却汉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一驯妄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧合砂,春花似錦青扔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缘屹,卻和暖如春凛剥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烈钞,地道東北人氯析。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像澈驼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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

  • RPC框架遠(yuǎn)程調(diào)用的實(shí)現(xiàn)方式在原理上是比較簡單的炊昆,即將調(diào)用的方法(接口名、方法名慕爬、參數(shù)類型窑眯、參數(shù))序列化之后發(fā)送到...
    謎碌小孩閱讀 3,102評論 0 13
  • 本篇文章面對的是有開發(fā)經(jīng)驗(yàn)的Java developer 因?yàn)槲覀儗⒁獙?shí)現(xiàn)的Spring的IOC容器,前些天由于工...
    Chinesszz閱讀 1,683評論 2 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法医窿,內(nèi)部類的語法磅甩,繼承相關(guān)的語法,異常的語法姥卢,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • 在上一篇文章卷要,我們講了 IoC 容器初始化的準(zhǔn)備階段,即找到 BeanDefinition 的 Resource ...
    偷星辰夜閱讀 1,281評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理独榴,服務(wù)發(fā)現(xiàn)僧叉,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139