前言

两天前写了文章《「造个轮子」——cicada(轻量级 WEB 框架)》 向大家介绍了 cicada 之后收到很多反馈,也有许多不错的建议。

同时在 GitHub 也收获了 80 几颗 小♥♥(绝对不是刷的。。)

也有朋友希望能出一个源码介绍,本文就目前的 v1.0.1 版本来一起分析分析。

没有看错,刚发布就修复了一个 bug,想要试用的请升级到 1.0.1 吧。

技术选型

一般在做一个新玩意之前都会有技术选型的过程,但这点在做 cicada 的时候却异常简单。

因为我的需求是想提供一个高性能的 HTTP 服务,纵观整个开源界其实选择不多。

加上最近我在做 Netty 相关的开发,所以自然而然就选择了它。

同时 Netty 自带了对 HTTP 协议的编解码器,可以非常简单快速的开发一个 HTTP 服务器。我只需要把精力放在参数处理、路由等业务处理上即可。

同时 Netty 也是基于 NIO 实现,性能上也有保证。关于 Netty 相关内容可以参考这里

下面来重点分析其中的各个过程。

路由规则

最核心的自然就是 HTTP 的处理 handle,对应的就是 HttpHandle 类。

查看源码其实很容易看出具体的步骤,注释也很明显。

这里只分析重点功能。

先来考虑下需求。

首先作为一个 HTTP 框架,自然是得让使用者能有地方来实现业务代码;就像咱们现在使用 SpringMVC 时写的 controller 一样。

其实当时考虑过三种方案:

  • 像 SpringMVC 一样定义注解,只要声明了对应注解我就认为这是一个业务类。
  • 用过 Struts2 的同学应该有印象,它的业务类 Action 都是配置到一个 XML 中;在里面配置接口对应的业务处理类。
  • 同样的思路,只是把 XML 文件换成 properties 配置文件,在里面编写 JSON 格式的对应关系。

这时就得分析各个方案的优缺点了。

方案二和三其实就是 XML 和 json 的对比了;XML 会让维护者感到结构清晰,同时便于维护和新增。

JSON 就不太方便处理了,并且在这样的场景并不用于传输自然也发挥不出优势。

最后考虑到现在流行的 SpringBoot 都在去 XML,要是再搞一个依赖于 XML 的东西也跟不上大家的使用习惯。

于是就采用类似于 SpringMVC 这样的注解形式。

既然采用了注解,那框架怎么知道用户访问某个接口时能对应到业务类呢?

所以首先第一步自然是需要将加有注解的类全部扫描一遍,放到一个本地缓存中。

这样才能方便后续的路由定位。

路由策略

其中核心的源码在 routeAction 方法中。

首先会全局扫描使用了 @CicadaAction 的注解,然后再根据请求地址找到对应的业务类。

全局扫描代码:

首先是获取到项目中自定义的所有类,然后判断是否加有 @CicadaAction 注解。

是目标类则把他缓存到一个本地 Map 中,方便下次访问时可以不再扫描直接从缓存中获取即可(反射很耗性能)。

执行完 routeAction 后会获得真正的业务类类型。

Class<?> actionClazz = routeAction(queryStringDecoder, appConfig);

传参方式

拿到业务类的类类型之后就成功一大半了,只需要反射生成它的对象然后执行方法即可。

在执行方法之前又要涉及到一个问题,参数我该怎么传递呢?

考虑到灵活性我采用了最简答 Map 方式。

因此定义了一个通用的 Param 接口并继承了 Map 接口。

public interface Param extends Map<String, Object> {

    /**
* get String
* @param param
* @return
*/
String getString(String param); /**
* get Integer
* @param param
* @return
*/
Integer getInteger(String param); /**
* get Long
* @param param
* @return
*/
Long getLong(String param); /**
* get Double
* @param param
* @return
*/
Double getDouble(String param); /**
* get Float
* @param param
* @return
*/
Float getFloat(String param); /**
* get Boolean
* @param param
* @return
*/
Boolean getBoolean(String param) ;
}

其中封装了几种基本类型的获取方式。

同时在 buildParamMap() 方法中,将接口中的参数封装到这个 Map 中。

Param paramMap = buildParamMap(queryStringDecoder);

业务执行

最后只需要执行业务即可;由于在上文已经获取到业务类的类类型,所以这里通过反射即可调用。

同时也定义了一个业务类需要实现的一个通用接口 WorkAction,想要实现具体业务只要实现它就行。

而这里的方法参数自然就是刚才定义的参数接口 Param

由于所有的业务类都是实现了 WorkAction,所以在反射时都可以定义为 WorkAction 对象。

WorkAction action = (WorkAction) actionClazz.newInstance();
WorkRes execute = action.execute(paramMap);

最后将构建好的参数 map 传入即可。

响应返回

有了请求那自然也得有响应,观察刚才定义的 WorkAction 接口可以发现其实定义了一个 WorkRes 响应类。

所有的响应数据都需要封装到这个对象中。

这个没啥好说的,都是一些基本数据。

最后在 responseMsg() 方法中将响应数据编码为 JSON 输出即可。

拦截器设计

拦截器也是一个框架基本的功能,用处非常多。

cicada 的实现原理非常简单,就是在 WorkAction 接口执行业务逻辑之前调用一个方法、执行完毕之后调用另一个方法。

也是同样的思路需要定义一个接口 CicadaInterceptor,其中有两个方法。

看方法名字自然也能看出具体作用。

同时在这两个方法中执行具体的调用。

这里重点要看看 interceptorBefore 方法。

其中也是加入了一个缓存,尽量的减少反射操作。

适配器

就这样的拦截器接口是够用了,但并不是所有的业务都需要实现两个接口。

因此也提供了一个适配器 AbstractCicadaInterceptorAdapter

它作为一个抽象类实现了 CicadaInterceptor 接口,这样后续的拦截业务也可继承该接口选择性的实现方法即可。

类似于这样:

总结

v1.0.1 版本的 cicada 就介绍完毕了,其中的原理和源码都比较简单。

大量使用了反射和一些设计模式、多态等应用,这方面经验较少的朋友可以参考下。

同时也有很多不足;比如传参后续会考虑更加优雅的方式、拦截器目前写的比较死,后续会利用动态代理实现自定义拦截。

其实 cicada 只是利用周末两天时间做的,bug 肯定少不了;也欢迎大家在 GitHub 上提 issue 参与。

最后贴下项目地址:

https://github.com/TogetherOS/cicada

你的点赞与转发是最大的支持。

「造个轮子」——cicada 源码分析的更多相关文章

  1. 「造个轮子」——cicada 设计一个配置模块

    前言 在前两次的 cicada 版本中其实还不支持读取配置文件,比如对端口.路由的配置. 因此我按照自己的想法创建了一个 issue ,也收集到了一些很不错的建议. 最终其实还是按照我之前的想法来做了 ...

  2. 「造个轮子」——cicada(轻量级 WEB 框架)

    前言 俗话说 「不要重复造轮子」,关于是否有必要不再本次讨论范围. 创建这个项目的主要目的还是提升自己,看看和知名类开源项目的差距以及学习优秀的开源方式. 好了,现在着重来谈谈 cicada 这个项目 ...

  3. 「从零单排canal 03」 canal源码分析大纲

    在前面两篇中,我们从基本概念理解了canal是一个什么项目,能应用于什么场景,然后通过一个demo体验,有了基本的体感和认识. 从这一篇开始,我们将从源码入手,深入学习canal的实现方式.了解can ...

  4. 「造个轮子」——设计 HTTP 请求全局上下文

    前言 本次 Cicada 已经更新到了 v1.0.3. 主要是解决了两个 issue,#9(Boss线程数好像设置有误 ) #8(怎么返回纯字符串内容不要JSON格式?). 所以本次的主要更新为: C ...

  5. 🏆【Alibaba微服务技术系列】「Dubbo3.0技术专题」回顾Dubbo2.x的技术原理和功能实现及源码分析(温故而知新)

    RPC服务 什么叫RPC? RPC[Remote Procedure Call]是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范.它允许程序调用另一个地址空间(通常是共享网络的另 ...

  6. 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)

    前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...

  7. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  8. jQuery.lazyload使用及源码分析

    前言: 貌似以前自己也写过图片懒加载插件,但是新公司使用的是jQuery.lazyload插件,为了更好的运用,自己还是把源码看了遍,分别记录了如何使用, 插件原理,各个配置属性的完整解释,demo实 ...

  9. dubbo源码分析5-dubbo的扩展点机制

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

随机推荐

  1. powershell_基础篇

    powershell 想必大家对windows操作系统下的cmd命令提示符可能并不陌生,大多数人都应该使用过它.而对于今天我们要学习的PowerShell跟cmd有什么关系呢?可以简单地说,Power ...

  2. DRAM的原理设计

    在一个电子系统中,CPU.内存.物理存储.IO这些单元必不可少,只不过有的集成在CPU内部,有的分离出来. 这里就针对系统中的内存,此处选用DRAM来进行说明,讲述下基本的原理设计,主要分为以下几个部 ...

  3. C#中IPAddress转换成整型int

    string addr = "11.22.33.44"; System.Net.IPAddress IPAddr=System.Net.IPAddress.Parse(addr); ...

  4. spring ref &history&design philosophy

    Spring Framework Overview Spring是开发java application的通用框架,分为多个模块(modules),核心是core container,包括configu ...

  5. 201771010126 王燕《面向对象程序设计(Java)》第十周学习总结

    实验十  泛型程序设计技术 实验时间 2018-11-1 1.实验目的与要求 (1) 理解泛型概念: 泛型:也称参数化类型(parameterized type),就是在定义类.接口和方法时,通过类型 ...

  6. 邮件服务器 postfix

    背景介绍 邮件服务器普遍需要一个主机名来使得mail from 以"账号@主机名"方式显示.由于外网上垃圾邮件太多,现在已不使用ip发邮件,很多网络供应商都会对来源不明的邮件进行限 ...

  7. 关于height、offsetheight、clientheight、scrollheight、innerheight、outerheight的区别

    二.也是平时经常用到的offsetheight 它返回的高度是内容高+padding+边框,但是注意哦,木有加margin哦,当然一般也木有啥需要把margin加进去的,以上代码为例,结果显示上图h2 ...

  8. 浅谈微信小程序一二

    1.生命周期 1.onLoad():页面加载时触发,一个页面只加载一次. 2.onShow():页面显示切换的时候触发 3.onReady():页面初次渲染完成时触发.一个页面只会调用一次,代表页面已 ...

  9. [微信小程序]编译.wxss出错,2 not found

    小程序新建项目就出错:2 not found  编译.wxss文件出错(不是一般的郁闷,新建项目就报错...) 大概的情况是开发工具没有更新.或更新不到, 第一,可以删掉开发工具重新下载最新安装: 第 ...

  10. Html元素添加事件禁用

    最近几天,测试在检测我页面功能时,疯狂点击带接口请求的按钮,然后就会发起无数次请求,然后app就卡住了.当看到这个问题的时候,心里疯狂鄙视测试(开个玩笑),一开始想的到解决方案是用函数防抖,使用函数防 ...