Vert.x
Vert.x特性
Vert.x诞生于2011年,比RxJava还要早,可以说是目前最古老的反应式编程框架之一。当时叫做`node.x,但之后改为了现在的名字,可能是因为和Node.js冲突吧。Vert.x是Eclipse软件基金会顶级Java开源项目之一,截止本书出版前,Vert.x已经发展到了4.5.x版。
官方介绍说Vert.x是一个运行于JVM平台之上的轻量级、高性能的异步无阻塞Java Web
开发框架,Vert.x依靠全异步的事件驱动框架(Netty](https://netty.io/),支持多种编程语言,非常适用于移动端后台、互联网及企业应用架构。
总之,Vert.x的作者对它用一句话给出了概括:JVM之上的反应式编程套件。
Vert.x是基于事件驱动的编程模型,使用Vert.x开发时,工程师们只需要编写事件处理器event handler即可。例如,当使用Vert.x编写TCP Socket应用时,如果Socket有数据输入或输出时,event handler会立即被创建并调用。
Vert.x用作业务处理时,基本上就对应于传统MVC中
的Service
层,但又不一样。因为Vert.x是全异步的,而传统的Service
业务逻辑基本上都是按照顺序同步
执行的,如果用Vert.x的方式用全异步来实现Service
,会导致业务逻辑看起来极其紊乱,根本没法读懂代码。所以Vert.x不适合融入到类似电商购物、交易对账这种流程冗长、逻辑严谨的业务中,反倒是非常适合于构建网络连接、消息传递这种天然就具备异步工作属性的业务服务。进一步来说,正是因为它天然就具备异步属性,所以在Vert.x的概念中,没有MVC
,没有三层架构
,没有AOP
,没有ORM
这种传统开发范式中才有的东西,它颠覆了很多东西。
在动手开之前,需要先重点说明或澄清一下Vert.x中几个比较重要的东西。
Verticle
:它是Vert.x中可被部署运行的最小化的业务处理引擎,可以把一个Verticle
当成是一个Service
实例。一个应用程序可以是由单个Verticle
或由EventBus
连接起来的多个Verticles
构成。Module
:一个Vert.x应用由一个或多个Module
组成,Module
几乎就等同于Maven多模块项目中的Module
,或者一个jar
包。EventBus
:它是Vert.x的核心,负责实现不同Verticle
之间的通信。Shared Data
:官方给出的文档说它是一种在各个不同应用之间安全地共享数据的方式,它保存共享数据的形式是Map
集合。

由Verticle
、Module
、EventBus
和Shared Data
所组成的Vert.x的框架,如下图所示。

运行Vert.x项目
Vert.x非常容易上手,习惯于使用Spring框架的读者,只要掌握了函数式编程范式,就能很快学会用Vert.x做开发。通过访问Vert.x网站,按照提示一步步地执行,可以用脚手架搭建一个最简单的Demo
。

单击App generator
之后,就可以创建Vert.x项目了。

接下来,通过单击页面上的Show dependencies panel
就出现了各种可供选择的依赖包。其中列出了诸如Web
、Data Access
、IoT
、Testing
之类的。

而下面的Advanced options
则可以指定Java版本。

在一般的互联网应用中,Web
和数据库
这两个依赖组件几乎是必选的,所以笔者在这里就先选中左边的Web
大类,然后再选择里面的Vert.x Web
依赖。至于Vert.x Web Client
是用来访问其他服务的,所以不用选。再选择Data Access
大类中的Reactive MySQL
,使用Vert.x无需JDBC。
选择好依赖包之后就可以单击Generate Project alt + ⏎
了。它会生成并下载一个starter.zip
压缩包,解压后导入到IDEA当中。

因为Vert.x项目的运行,既不同于普通的Java项目那样直接运行main()
方法启动,也不同于SpringBoot项目那样用注解指定应用入口SpringBootApplication
,而是需要工程师自行手动设置,告诉IDEA哪个是MainClass
。
想把Vert.x项目跑起来,先要上图中红色框部分的内容拷贝下来,也就是下面代码段的内容。
<main.verticle>com.java.book.chapter06.MainVerticle</main.verticle>
<launcher.class>io.vertx.core.Launcher</launcher.class>
这里的<main.verticle>
是创建项目时指定的,而<launcher.class>
是固定不变的。
然后在IDEA的右上角单击Edit Configuration
,弹出增加配置界面。

在这里增加一个新的Application
配置。

创建后在Name
框中可以输入一个应用名称。在Main Class
输入框中填写<launcher.class>
标签里的内容,而在Program arguments
中则写上run
和<main.verticle>
标签里的内容。输入完后单击OK
保存。

这里要记得输入时不要忘了在<main.verticle>
标签内容的前面加上run
。
配置完成后,就可以通过单击右上角的绿色三角形运行程序了。

因为Vert.x项目默认在8888
端口启动,所以可以在浏览器中访问http://localhost:8888地址,就能看到网页上出现的内容Hello from Vert.x!
。这里笔者没有修改任何代码,出现的内容都是Vert.x默认的。
创建GET或POST接口
因为刚才的代码只是启动了一个HTTP服务,但并未为这个服务创建任何的可访问接口,例如http://localhost:8888/interface,所以,接下来就来实现它。
对于任何应用来说,跟在端口后面的路径,本质上都是路由。不同的路由就决定了不同的访问地址和访问方法。例如http://localhost:8888/getUser?username=admin和http://localhost:8888/getUser,虽然接口很类似,但其实是两个不同的路由,也称URL为访问路径
。因此如果需要创建能够让外部应用访问的接口,那么首先就要创建一个路由。
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) {
// 创建路由
Router router = Router.router(vertx);
......
}
}
代码中的成员变量vertx
,是MainVerticle
的父类AbstractVerticle
已有的成员变量,它是整个Vert.x极为核心的一个类,它的作用类似于Spring的BeanFactory
,负责创建I/O
服务,管理定时器,获取对事件总线API
的引用,获取文件系统引用,获取共享数据的引用,部署或卸载应用中的Verticle
等,它是整个Vert.x应用的入口类。
所有继承自AbstractVerticle
的子类都可以直接使用它,而Router
类则是专用于接收HTTP请求并将其分发到对应的路径中。
对于刚接触Vert.x的新手,如果不熟悉这些类的用法也没关系,官网已经提供了非常详细的指导。
访问https://vertx.io/docs,找到对应的Vert.x Web
部分。

单击进去之后,就能在左边找到Routing by HTTP method
项,然后再单击它,就能看到官网上对此的详细示范了。

接下来,就参照官方的示例代码,开发自己的HTTP接口。
package cn.javabook.chapter06;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) {
// 创建路由
Router router = Router.router(vertx);
// 创建一个GET请求
router.route(HttpMethod.GET, "/vertx/get")
.handler(context -> {
MultiMap queryParams = context.queryParams();
String username = queryParams.contains("username") ? queryParams.get("username") : "unknown";
String password = queryParams.contains("password") ? queryParams.get("password") : "unknown";
context.json(
new JsonObject()
.put("username", username)
.put("password", password)
);
});
// 创建一个POST请求(用Postman测试)
router.route(HttpMethod.POST, "/vertx/api/:id/:username/")
.handler(context -> {
String id = context.pathParam("id");
String username = context.pathParam("username");
System.out.println(id + " - " + username);
context.json(
new JsonObject()
.put("errcode", "200")
.put("message", "success")
);
});
// 创建HTTP服务
vertx.createHttpServer().requestHandler(router).listen(8888, http -> {
if (http.succeeded()) {
startPromise.complete();
System.out.println("HTTP server started on port 8888");
} else {
startPromise.fail(http.cause());
}
});
}
}
然后用Postman以GET
方法访问http://localhost:8888/vertx/get,以POST
方法访问http://localhost:8888/vertx/api/{id}/{username}/。
注意POST
方法后面要带上/
,因为代码中的URI字符串就是/vertx/api/:id/:username/
,如果不加上/
则会出现Resource not found
的错误。
访问MySQL
如果仅仅只是访问一下接口,那肯定是无法满足一般Web
应用的需求的,它至少要能存储与读取数据。
所以,现在就来给Vert.x加上操作MySQL数据库的功能。还是和之前的方式一样,如果觉得无从下手,仍然可以从官方的示例文档开始。


可以直接拷贝官方示例文档中的代码。但需要注意的是,直接使用示例代码会出错。

抛出的异常内容是Running in a Vertx context => use Pool#pool(Vertx, SqlConnectOptions, PoolOptions) instead
,意思是数据库连接池没有使用vertx
上下文。也就是需要在连接池中加上AbstractVerticle
的成员变量vertx
,让它成为上下文。因此这段官方给出的代码需要在创建连接池时增加一个using(vertx)
方法。
// 创建连接池
SqlClient client = MySQLBuilder
.client()
.with(poolOptions)
// 这里要记得加上vertx上下文,否则抛异常
.using(vertx)
.connectingTo(connectOptions)
.build();
访问MySQL的完整代码如下。
package cn.javabook.chapter06;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.mysqlclient.MySQLBuilder;
import io.vertx.mysqlclient.MySQLConnectOptions;
import io.vertx.sqlclient.*;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) {
// 创建路由
Router router = Router.router(vertx);
// 创建一个GET请求
router.route(HttpMethod.GET, "/vertx/get")
.handler(context -> {
MultiMap queryParams = context.queryParams();
String username = queryParams.contains("username") ? queryParams.get("username") : "unknown";
String password = queryParams.contains("password") ? queryParams.get("password") : "unknown";
context.json(
new JsonObject()
.put("username", username)
.put("password", password)
);
});
// 创建一个POST请求(用Postman测试)
router.route(HttpMethod.POST, "/vertx/api/:id/:username/")
.handler(context -> {
String id = context.pathParam("id");
String username = context.pathParam("username");
System.out.println(id + " - " + username);
context.json(
new JsonObject()
.put("errcode", Constant.HTTP_STATUS_OK)
.put("message", "success")
);
});
// 创建HTTP服务
vertx.createHttpServer().requestHandler(router).listen(8888, http -> {
if (http.succeeded()) {
startPromise.complete();
System.out.println("HTTP server started on port 8888");
} else {
startPromise.fail(http.cause());
}
});
// 创建MySQL连接
MySQLConnectOptions connectOptions = new MySQLConnectOptions()
.setPort(3306)
.setHost("172.16.185.166")
.setDatabase("javabook")
.setUser("root")
.setPassword("123456");
// 连接池选项
PoolOptions poolOptions = new PoolOptions().setMaxSize(5);
// 创建连接池
SqlClient client = MySQLBuilder
.client()
.with(poolOptions)
// 官方源码中没有,但这里要记得加上vertx,否则抛异常
.using(vertx)
.connectingTo(connectOptions)
.build();
// 简单查询
client.query("SELECT * FROM user_info")
.execute()
.onComplete(ar -> {
if (ar.succeeded()) {
RowSet<Row> result = ar.result();
System.out.println("获取到了 " + result.size() + " 行数据");
for (Row row : result) {
System.out.println("id = " + row.getString("id"));
}
} else {
System.out.println("Failure: " + ar.cause().getMessage());
}
// 关闭连接池
client.close();
});
}
}
启动服务,可以直接在控制台看见访问数据库的数据。前提是数据库和表都必须存在,这里的test
表的表结构可以随意创建,只要能看到数据就行。
也可以把这段代码放到接口中去,或者抽象出来做成Service
服务,这样就可以形成较为完整的功能应用了。
感谢支持
更多内容,请移步《超级个体》。