「造个轮子」——cicada 源码分析

前言

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

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

也有朋友但愿能出一个源码介绍,本文就目前的 v1.0.1 版原本一块儿分析分析。github

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

技术选型

通常在作一个新玩意以前都会有技术选型的过程,但这点在作 cicada 的时候却异常简单。设计模式

由于个人需求是想提供一个高性能的 HTTP 服务,纵观整个开源界其实选择很少。缓存

加上最近我在作 Netty 相关的开发,因此天然而然就选择了它。服务器

同时 Netty 自带了对 HTTP 协议的编解码器,能够很是简单快速的开发一个 HTTP 服务器。我只须要把精力放在参数处理、路由等业务处理上便可。app

同时 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 参与。

最后贴下项目地址:

github.com/TogetherOS/…

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

相关文章
相关标签/搜索