前言
最近了解到了vertx这个异步框架,但平时用的比较多的还是spring,出于好奇,尝试基于vertx web去实现spring mvc风格注解。
最终效果如下所示
1 | 4j |
@RestContoller
现在主流spring mvc的控制器一般是使用@RestController
注解,它的主要作用是告诉框架这个类是请求控制器,让框架主动扫描并加载这个类。
这个注解本质是个标记注解,所以目前不需要其他字段。
1 |
|
由于目前没有实现任何DI(依赖注入),为了方便我们直接在代码里面手动创建实例并加载。
1 |
|
上面这段代码是在Verticle里面启动一个HTTP服务器,并进行路由构建。里面的routerMapping
方法我们下面实现一下
1 | private <ControllerType> void routerMapping( |
先简单判断下这个加载的类是否为带@RestController
注解。好了,@RestContoller
注解的任务已经完成了。
@RequestMapping
@RequestMapping
注解的作用是把请求路径和我们的处理逻辑关联在一起。
1 |
|
value对应的path,而method对应http的请求方法,如GET、POST等。使用效果如下所示:
1 | "post/body", method = HttpMethod.POST) (value = |
我们继续上一节的路由解析。
首先我们要获取到控制器类中被该注解标记的方法
1 | Method[] methods = clazz.getDeclaredMethods(); |
这里使用了反射,并且只处理了带@RequestMapping
注解的方法,然后从注解中获取请求路径和请求方法,具体代码如下所示
1 | // ... handle method code |
httpMethods这个参数我们采用了数组类型,这样一个处理函数可以绑定到多个HttpMethod中。这里还做了一个特殊处理,当用户省略这个参数时,将绑定全部HttpMethod,毕竟不绑定HttpMethod的处理函数没有意义。
上面代码中的requestHandler
的功能是调用被注解的处理函数并注入所需要的参数,并将函数返回值转换为合适的格式再返回给请求者。个人感觉强大的参数注入功能可以极大的提升开发者的编码体验。接下来我们实现下requestHandler
。
返回值处理
返回值一般有多种情况:
- void类型,这种是无返回值,一般是特殊情况会使用。比如下载文件,无法以JSON形式返回,需要调用RoutingContext进行处理。
- 基础类型和字符串,直接转换为JSON中的数字和字符串就好。
- 其他类型,当POJO处理,直接序列化成JSON
处理返回值代码如下所示:
1 | // Write to the response and end it |
Void类型就直接return,不做处理。
再判断是否为字符串类型,字符串类型可以直接作为返回内容,其余类型走JSON序列化。
参数处理
vertx请求参数的内容都可以通过RoutingContext对象获取到,无论是在URI里的query还是body里面的formdata或者JSON形式数据,都是可以的。
1 | MultiMap params = ctx.request().params(); |
一个典型的请求处理函数如下所示:
1 | "echo") ( |
echo 函数有4个参数,我们需要对它进行反射获取4个参数的类型和名字。获取类型可以方便我们对数据进行类型转换,对于一些特殊的类型还有进行专门的处理。参数类型比较好获取,反射API可以直接获取到
1 | Method[] methods = clazz.getDeclaredMethods(); |
参数名字就相对复杂一点,虽然JDK8里面提供了对方法形参名反射的API,但它存在限制
1 | Parameter[] parameters = method.getParameters(); |
如果在编译代码的时候没有加上–parameters
参数,那么Parameter#getName
拿到的是arg0
这样的占位符,毕竟java还是要向前兼容的嘛。
正常途径还真不好拿到形参名,所以像MyBatis这类的框架是让用户通过注解进行形参名标注
1 | public interface DemoMapper { |
@Param
允许用户使用和形参名不一样的值,类似别名,功能上更加灵活,但是大部分情况下,我们就希望直接使用形参名,重复的内容写两遍还是比较不友好的。
但是spring就支持直接获取方法形参名。在强大的搜索引擎帮助下,可以发现spring里面使用了字节码方式去获取方法形参(具体内容请自行google),这里我们使用javassist来实现这个功能,这样就不需要自己去处理字节码了。
1 | // javassist获取反射类 |
javassist
可以获取到方法的LocalVariableAttribute
,这里面有我们需要的形参信息,包括形参名。
获取到对应的形参名后,我们可以靠这个获取请求的参数:
1 | // 单个值 |
接着我们来注入参数值,需要处理的参数类型可以分为下列几种:
- 简单类型,基础类型和它们的包装类,再加上字符串
- 集合类型,
java.util.Collection<T>
及其子类,泛型参数T支持简单类型 - 数组类型,数组的组件类型(
ComponentType
)支持简单类型 - 数据类型,类似POJO的数据载体类,里面可以再嵌套数据类型或其他类型
- 特殊类型,比如FileUpload和RoutingContext,需要特殊处理
简单类型处理只需要把基础类型都转换为包装类型,再反射调用valueOf
方法就可以了
1 | private <T> T parseSimpleType(String value, Class<T> targetClass) throws Throwable { |
集合类型,反射获取到泛型类型后按简单类型处理
1 | private Collection parseCollectionType(List<String> values, Type genericParameterType) throws Throwable { |
数组类型,反射获取到组件类型后按简单类型处理
1 | if (paramType.isArray()) { |
数据类型,反射获取类型中字段的类型,递归处理
1 | private Object parseBeanType(MultiMap allParams, Class<?> paramType) throws Throwable { |
特殊类型,其中RoutingContext
类型不会作为请求参数,而是由route中获取并注入。
1 | if (paramType == RoutingContext.class) { |
好了,现在@RequestMapping
的功能已经实现了。
@RequestBody
默认情况下用户进行POST请求,数据是以表单形式提交的,有些时候我们需要以JSON形式提交,那么本注解就是实现这样的功能。
1 |
|
这里处理方式比较简单,把整个body以字符串类型读取并进行反序列化
1 | List<? extends Class<? extends Annotation>> parameterAnnotation = Arrays |
Spring注解挺多的,这次就先实现这个几个吧,剩下的后面再写。