跟我动手搭框架三之Web容器实现

作者: Chinesszz | 来源:发表于2017-12-20 21:49 被阅读13次

    本篇主要对Web的实现做说明,在参考文章的同事,可以把code clone下来,看,代码中有很多需要优化的地址,我已经用TODO标记处理啊,小编会不断的进行优化和分析,演示SmileBootDemo也可以git clone,debug学习

    Smile源码地址

    SmileBootDemo

    目录

      1. 核心描述类介绍
      1. Smile启动核心实现
      1. Http请求多线程异步实现
      1. 下一篇主要介绍内容
      1. 扩展

    1. 核心描述类,主要保存处理方法及参数类型

    其实所有方法的执行,都离不开ioc的实现,IOC主要将组件(被@SmileComponent标记过的都为组件)保存为BeanDefinition的形式,而组件中的method,主要保存为WebDefinition的形式,而method的参数名称,参数类型,参数位置索引,主要保存在ParamterDefinition,对于请求的处理,就是根据url找到对应的method,然后根据ParamterDefinition将请求参数,转换成参数原本类型,然后处理

    描述类 说明 存放位置
    BeanDefinition 保存组件Class字节码及实例化对象 Map<String(beanName), BeanDefinition> registeredBeans
    WebDefinition 保存Url及对应的处理方法,及实例化对象,及参数类型 Map<String(url), WebDefinition> webHandlerMaps = new ConcurrentHashMap<>();
    ParamterDefinition 保存参数位置索引及参数名称,参数类型,参数注解 WebDefinition.ParamterDefinition

    2. Smile启动核心

    • 2.1 SmileApplicationContext 扫描所有组件,并check 是否需要代理,最终生成IOC容器

      IOC实现

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

    • 2.2 SmileApplicationContext生成IOC容器之后,加载是否有实现ExtApplicationContext扩展类scanExtContext()方法

      WebApplicationContext此时会执行,并扫描被@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异常,请检查,请填写需要绑定的url地址");
              }
              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处理实现核心

    • 3.1将每个请求生成一个MessageRequest及MessageResponse,作为一个任务交给线程池去异步执行

      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 是执行策略,当为ture时候,为rpc默认,可以定义自己的rpc远程调用的方式实现结果返回

      public class DefaultTaskProcessChoice implements SmileTaskChoice {
          private boolean isRpc = false;
          private MessageRequest messageRequest;
          private MessageResponse messageResponse;
      
          /**
           *
           * @param request
           * @param response
           * @param isRpc 本地方法:false rpc调用: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 ,拿到执行方法,将请求参数,绑定到方法,作为实参,传递

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

    并通过Netty 连接通道Channel把处理结果返回给客户端

    • 3.4 SmileMessageExecutor.submit 方法中监听任务是否成功处理,成功并通过Netty 连接通道Channel把处理结果返回给客户端

       public static void submit(Callable<Boolean> task, final ChannelHandlerContext ctx, HttpRequest metaRequest, final MessageRequest request, final MessageResponse response) {
      
              /**
               * SmileThreadFactory 目的构建自己的线程名,并通过线程组进行统一管理
               * SmileThreadPoolExecutor 构建自己的线程池,对任务进行,细微管理
               */
              if (threadPoolExecutor == null) {
                  SmileThreadPoolExecutor smileThreadPoolExecutor = new SmileThreadPoolExecutor(new SmileThreadFactory("Smile"));
                  ThreadPoolExecutor executorService = (ThreadPoolExecutor) smileThreadPoolExecutor.getExecutory();
                  threadPoolExecutor = MoreExecutors.listeningDecorator(executorService);
              }
              /**
               * 处理完成任务如果任务完成就,渲染出去
               */
              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. 下一篇主要介绍内容

    框架的实现方案属于传统的MVC机构,只不过吧视图层V取消掉了,这也是趋势,前后分离,后端只做关心数据处理,

    而传统的MVC架构,核心为Servlet,SpringMVC核心为DispatchServlet,是对原始Java Servlet的一个封装,关于这点可以看小编的另一篇文章手写一个轻量级的网关API,当然使用Netty也是如此,我们的入口就是HttpDispatchServerHandler 核心方法就是messageReceivedDispatch

    而只知道,这些是远远不够的,Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,我会新开二篇,专门介绍IO模型重点介绍IO multiplexing(IO多路复用)Netty如何工作 ! 包括如何实现,心跳检测

    主要会介绍下面写模块

    • 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 根据启动配置,当如果是rpc服务就要使用MessageProcessTask
                 * 如果是本地服务使用LocalMessageTask
                 *
                 * 此时MessageRequest和MessageResponse都是final 修饰,目的是保证始终是对当前的MessageResponse
                 */
                SmileTaskChoice smileTaskChoice = new DefaultTaskProcessChoice(messageRequest, messageResponse, false);
                /**
                 * //TODO 交给线程处理异步处理响应
                 */
                SmileMessageExecutor.submit(smileTaskChoice.choice(), ctx, req, messageRequest, messageResponse);
            }
        }
    

    扩展

    再次声明小编也是一个菜鸟,是一只具有学习精神,并热爱编程的菜鸟, 所有的文章都是经过参考很多优秀博文,给我带来的进步,小编,希望将学习到的所有知识点,也分享给大家 ! 小编会在这里列出,参考到的优秀博文,尊重每位知识传播者的劳动果实.

    如果您发现小编文章中,有错误,请及时指出,并通知小编改正,小编在此谢过.

    欢迎继续关注小编~ 小编努力coding…

    参考

    Smart Framework 设计动力来源

    segmentfault-Netty 源码分析 Netty强化学习

    SpringIOC源码分析 描述类灵感来源

    基于Netty打造RPC服务器设计经验谈

    相关文章

      网友评论

        本文标题:跟我动手搭框架三之Web容器实现

        本文链接:https://www.haomeiwen.com/subject/bvjbwxtx.html