Spring Boot 转 Vert.X 随笔

本文记录了将一个Spring Boot MVC项目转换为Vert.X的过程,探讨了两者在项目结构、处理流程上的差异。在Spring Boot中,请求经过controller、service、repository层层同步调用。而在Vert.X中,使用EventLoop和EventBus实现异步处理,controller通过EventBus与service、repository通信。文章强调了Vert.X中避免标准verticle阻塞的重要性,以及如何利用Worker verticle处理阻塞操作,如数据库交互。同时,介绍了Vert.X中Repository层的异步JDBC操作和model层的挑战。
摘要由CSDN通过智能技术生成

最近上尝试了把一个典型的 spring boot mvc 项目(提供静态文件/RESTful服务和只依赖sql数据库)转成用 vert.x 编写,发现了一些问题,至此记下。

Spring Boot web 和 Vert.X web 的结构

一个普通的 Spring web mvc 项目结构类似这样:

  • controller层: 控制url路由映射,request、session、response 的读取、写入
  • service层: 封装了 repository 层并提供服务给 controller 层,执行业务相关的逻辑
  • repository层: 封装了对数据库的调用,并处理数据库表/行和POJO之间的映射
  • model/entity层: repository 层用来把数据库表/行映射的 POJO

一个 client request 过来,依次经过 controller -> service -> repository -> service -> controller(忽略web容器的TCP/HTTP处理),它们都是同步阻塞调用的,所以使用起来非常方便、易于理解和调试,但这也是和 Vert.X 最不同的地方。

一个 Vert.X web 项目结构类似这样:

额。。。好吧😂,我还没有见过比较规范性的 Vert.X web 项目目录,说下我自己设计的吧

在 vertx web 中,一个 client request 过来,会由 EventLoop 捕获,解析 url 交给对应的处理器就返回了继续 EventLoop 了,处理器或者叫 controller 异步调用 service (通过事件总线并写一个异步结果处理器)就返回了,service 再调用 repository,当repository(可能异步)获取到数据库结果后,返给 service, service 通过消息总线返还结果给 controller,controller 西德异步结果处理器会处理这个结果,比如把它放到 response body 里并表示 response end。之后,vertx web 的 HTTP 服务器会把这个 response 按HTTP协议封装好发给 client。

首先需要了解 Vert.X 的实例单元 verticle 分为三种

援引: Vert.X逐鹿记 https://lang-yu.gitbook.io/vert-x/01-index/01-4-verticle-instance

1.2. Verticle的分类

前文一直提到了Verticle的分类,但我们都没有梳理过,这里先谈谈它的分类。前边章节我们已经介绍了Actor模型,那么这个章节我们需要对Actor给个定义:Vert.x中的Actor(Verticle组件)是有种类的。根据官方教程的描述,Verticle主要分为三种:

Standard:标准类型,我称之为“哨兵”,在Vert.x中,它运行的线程池称为Event Loop,它有一个典型特征就是负责“监听”客户端产生的事件,在该机制中,客户端所有的请求都可抽象成事件(Event)。您可以在编程过程中,将您的代码放心大胆地放到Verticle中执行,因为Vert.x中的Verticle是线程安全的,它的每一个处理事件的处理器(Handler)独占单个线程,您可以把您编写的代码当成单线程代码来处理,甚至于不需要去考虑线程同步的问题。

Worker:工作线程,我称之为“士兵”,在Vert.x中,这种类型的Verticle运行的线程池称为Worker Pool,它不会直接处理客户产生的事件,只会接收Event Bus中发生的事件并去处理,在该机制中,Event Bus中所有的消息都可抽象成事件(Event)。——有时候这样的Verticle可以用于处理后台任务,而官方的推荐是工作线程本身是为了后台一些阻塞式代码(访问文件系统、数据库、网络)设计的,所以对于这种任务场景,尽可能使用Worker类型的Verticle

Multi-Threaded Worker:另一种工作线程,我称之为“神兵”,在Vert.x中,这种类型的Verticle依然是一种Worker,唯一不同的是它可以被多个线程同时执行,所以可以当做“升级的士兵”来对待,为此我们称为“神兵”。

也就是说我们的 HTTP服务器,或者说是 mvc 中的 controller层,处理url路由和 request/session/response 的,是属于 Standard verticle,对于 standard verticle 来说,重要的就是不要阻塞它,也就是说你不能像 spring mvc 里的那样直接在 controller 的方法里使用 service 对象调用阻塞的数据库服务。
而负责调用数据库的就是 worker verticle(通过vertx.deployVerticle(ServiceVerticle.class, new DeploymentOptions().setWorker(true).setInstances(4));可设定),它支持执行阻塞代码。(BTW:无论standard还是worker verticle,当某段代码执行时间超过一定阈值还没结束时,都会抛出一个阻塞时间过长的异常,可能有错欢迎指正)

这里还是介绍下我的目录结构,比较简单,名字还是有点延续 spring mvc 的,可能以后技术长进了会变

1. controller层

它的功能和 spring mvc 的类似,只不过模式不同。

在 spring mvc 中,是基于 servlet 的 web 服务器(3.0/3.1之后也提供了异步处理和非阻塞IO能力),我们通常使用注解来实现配置路由、request/response参数等,使用实例变量来直接调用 service 服务并获取结果。

而 vert.x web 并不是这样,vert.x 的 web 服务器是 Netty(插句话 Spring WebFulx 也是 Netty,不过它能用 spring mvc 的一些注解,所以写起来比 vert.x web 舒服),路由映射配置不支持注解需要在方法内直接写,并配置对应的的 Handler(函数式接口)。
spring mvc 的控制器单元是一个个加了@RequestMapping()的方法,而 vert.x web 的控制器单元是一个个 Handler<RoutingContext> 实现类(当然它是个函数式接口,因此用方法引用的话就跟编写方法一样了);spring mvc 的方法里参数是 HttpRequest 和 HttpResponse,或者我们更通常用 @PathVariable 等注解使用它们而省去 HttpRequest, 相比 spring mvc 可以有多个方法参数 vert.x web 的方法参数只有一个 RoutingContext ,它里面包含了很多东西,获取 request、reponse、cookie、session、路径或请求参数、request body等都有直接 api 非常容易(也支持路由转发),能给你很大的自由度。

vert.x web 是一个 Standard verticle,因此你不能直接放进去一个 service 实例变量,让控制器方法去调用它来操作数据库,因此要想和 service 层通信,需要使用 VertX 的事件总线 EventBus。

这里简单介绍下 EventBus(详细的可看Vert.X逐鹿记),如果说 Spring 的核心之一是使用 IOC/DI 来实现应用解耦,那么Vert.X就是使用 EventBus 来实现应用解耦,它是个牛逼的东西。在 DI 中,Bean 之间只依赖接口,且容器自动实例化具体的接口实现并注入给需要的Bean,让我们不用每次都自己手动管理各个依赖。但 Vert.X 的想法并不是这样,你都不需要知道你的依赖长什么样,你只需要往 EventBus 上喊一句话“我要这个数据”,然后就等待别人去回你(对应请求响应通信),或者大喊一句“伙计们帮我打印下这个日志”,然后就不用管了会有群人来帮你打印(对应发布订阅模型)。基于EventBus的解耦,有一个很大的优点,这是 spring 的 DI 做不到的,EventBus 可以是分布式的、跨语言的。也就是说,每个发送给 EventBus 的消息需要实现 ClusterSerializable 接口(通常String、JsonObject、JsonArray就够用了,你也可以自己实现该接口),来保证能够序列化和反序列化在各节点之间传送,而且你用 java 向一个 EventBus 里发送一个消息,我用 js或python 可以直接从 EventBus 里获取到这个消息。PS: 我写过一个实现 EventDriver 和 EventBus 的文章,大家可以瞅瞅 Event Driven 模式详解与一个设计实现

我们在 controller 层的 Handler 里通过向 EventBus 指定地址发送一个 request,
之后写一个回调处理器处理 response 可以完成通信,收到该 request 并处理、回复的是 service 层的 worker verticle,它通过在 EventBus 的该地址注册消费者做到此时,这样就能完成并解耦 controller 和 service 的通信、调用。

比如一个 Handler 返回一个关于学生信息的 json 响应,它通过 EventBus 来调用位于 service 层的服务。

    /**
     * 返回指定 `id` 的学生基本信息,id为学号
     */
    void respondInfoById(RoutingContext routingContext){
   
        String id = routingContext.pathParam("id");
        bus.<JsonObject>request("topic.studentinfo.id", id, reply -> {
   
            if(reply.succeeded()){
   
                JsonObject student = reply.result().body();
                routingContext
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值