12.6 微服务平台Vert.x
Vert.x是一个用于JVM开发的现代微型Web服务框架,最初由VMWare开发,但现在是一个Eclipse Foundation项目。Vert.x是一个货真价实的多语言框架,提供了针对Java 、Groovy、Scala、Kotlin等多种JVM语言以及Nashorn(JavaScript)、JRuby和Ceylon的官方文档。
Vert.x的核心目标是高性能和可伸缩性,为此它使用了类似于Node.js的异步编程模型。简而言之,Vert.x有一个等待事件发生的主事件循环,事件发生时,它调用注册的事件处理程序对其进行处理。你在应用程序中编写的事件处理程序应尽快返回,因为这些代码执行时,Vert.x的主事件循环将无法接收并处理新事件。
如果系统中所有的事件处理程序都能快速处理事件,单个Vert.x应用程序实例就能处理数百乃至数千个请求。在Node.js和Python中,故事到这里就结束了。需要更强大的威力时,必须运行多个独立的应用程序实例,因为这些语言无法充分利用多核CPU。这浪费了宝贵的内存和资源。Vert.x运行在JVM上,能够在不同的CPU内核中运行单个应用程序实例中的多个事件循环,从而充分发挥当今CPU的威力。为简化开发工作,一个事件处理程序通常只能在单个事件循环中使用。因此,开发人员通常根本不用考虑复杂的并发和多线程问题。
还有其他的可能性。例如,可让基于Vert.x的应用程序的多个实例组成集群,集群中的实例可以运行在同一台机器上,也可以运行在不同的服务器上。
有时候,有些操作可能需要很长时间才能完成。例如,向繁忙的数据库服务器查询并对返回的复杂数据结构进行计算时,无法保证事件处理程序将快速返回。对于这样的情形,Vert.x提供了简单的解决方案:将长时间运行的任务委托给独立的工作线程。事件循环将任务委托给工作线程,自己则继续处理其他事件。每过一段时间,它就会检查任务是否已完成,如果已完成,就根据计算得到的结果对相应的事件进行处理。由于可用的线程数是可以配置的,因此在基于Vert.x的应用程序中,能够妥善地管理可用的服务器资源(内存、线程等)。
12.6.1 在文件ivy.xml中添加Vert.x依赖
为了确定必须提供的有关Vert.x的依赖信息,我们将采用前面获悉H2数据库系统依赖信息的方法:使用Maven的在线搜索引擎(http://search.maven.org)。
为获取正确的依赖信息并将其添加到文件ivy.xml中,请执行如下操作。
- 访问前述URL并搜索vertx-core。
- 找到Groupid为io.vertx、ArtifactId为vertx-core的条目,并单击其版本号(编写本书期间为3.4.1)。
- 在产品详情(artifact details)页面中,单击Apache Ivy,并将相应的XML条目(我看到的是
复制到剪贴板中:

- 在Eclipse中,打开文件ivy.xml file,并将前面复制的内容粘贴到H2数据库的
条目后面。
为让Ivy下载必要的依赖并将其添加到项目的类路径中,请执行如下步骤。
(1) 右击文件ivy.xml并选择Add Ivy Library…。
(2) 单击Finish按钮。Ivy将下载必要的依赖。
12.6.2 创建Web服务
首先,在文件Main.groovy的开头(package语句后面)添加必要的import语句:
package webserviceimport java.sql.DriverManagerimport groovy.sql.Sqlimport io.vertx.core.AbstractVerticleimport io.vertx.core.Futureimport io.vertx.core.Vertximport io.vertx.core.http.HttpMethod
能够处理Vert.x事件的类被称为Verticle。通过扩展Vert.x框架提供的抽象类AbstractVerticle,可轻松地让类变成Verticle。为此,请找到下面的代码行:
class Main {...}
将其修改成下面这样:
class Main extends AbstractVerticle {...}
在抽象类AbstractVerticle中,最重要的方法是start(),它在Vert.x初始化Verticle时被调用,你应使用它来启动Vert.x内置的HTTP服务器以及注册事件处理程序。这里也将分步创建这个方法。首先来定义方法start()本身:
public void start(Future<Void> fut) {}
传入的Future对象用于让Vert.x知道Verticle是否成功地初始化并启动了自己。在这个示例中,我们需要设置一个HTTP服务器,而设置和配置好一切可能需要一段时间。Vert.x不会等待方法start()返回,而是在Verticle初始化期间继续执行其他任务。这要求我们在启动后调用Future对象的方法complete(),而在没有启动时调用fail()。
AbstractVerticle类提供了一个名为vertx的变量,我们可使用它来与Vert.x框架通信。下面首先来编写设置内置HTTP服务器的代码,为此在方法start()中添加如下代码:
Vertx.createHttpServer().requestHandler() { request ->}.listen(8080) { result ->if (result.succeeded()) {fut.complete()} else {fut.fail(result.cause())}}
抽象类AbstractVerticle提供的变量vertx指向一个实现了io.vertx.core.Vertx接口的类的实例,而这个类的每个方法都返回变量vertx指向的对象。这让你能够串接对这个对象的方法调用。要不是因为这一点,我们就必须在全部三个方法调用前面都加上vertx。
我们让HTTP服务器侦听端口8080。HTTP服务器初始化完毕后,将调用传递给方法listen()的闭包。服务器能够成功地启动时,其方法succeeded()将返回true;在这种情况下,将对传递给方法start()的Future对象调用方法complete()。这样Vert.x便知道Verticle已准备就绪了。如果HTTP服务器无法启动(例如,由于端口8080已被其他应用程序占用),将调用Future对象的fail()方法,这将导致应用程序停止执行。
方法requestHandler()指定一个HTTP请求处理程序;Vert.x主事件循环遇到HTTP请求时,将调用通过参数传递给这个方法的闭包。有一些Vert.x扩展,它们添加了复杂的路由器功能,使得能够预先注册URL,这让Vert.x能够与Express(Node.js)和Flask(Python)等流行的Web应用程序框架媲美。这里不会使用这些扩展,因为就这个简单的示例项目而言,根本不需要它们。
下面来编写HTTP请求处理程序,为此在.requestHandler(){ request -> }块中添加如下代码:
if (request.path() == "/blogs/" && request.method() == HttpMethod.GET){request.response().putHeader("content-type", "application/xml").end( generateXML().toString() )} else {request.response().setStatusCode(404).end("Error 404");}
request对象的所有方法都返回它,因此也可串接通过这个对象发起的方法调用。这些代码非常简单:如果请求是对URL /blogs/的GET HTTP请求,就调用返回XML数据的函数generateXML();否则返回HTTP代码404(页面未找到)。
再来详细地说说方法generateXML()。它新建一个数据库连接、生成XML并关闭数据库连接。在大型应用程序(尤其是多线程大型应用程序)中,建议不要共享JDBC数据库连接。在这里,我们的做法是为每个请求都新建一个连接,并在处理完请求后关闭连接。就这里这样的小型示例而言,这没什么问题,但在更复杂的Web应用程序中,应使用连接池系统。新建数据库连接的开销很高,而通过使用连接池系统,可确保未用的连接被归还给连接池以便重用。在JVM平台中,可使用的连接池系统有很多。
现在余下的唯一工作是确保Verticle已启动。一种选择是直接在JVM方法static main()中完成这项任务。为此,重写方法main(),使其类似于下面这样(删除代码行println(app.generateXML())并添加突出显示的代码行):
static public void main(String[] args) {def app = new Main()def connection = app.createDatabaseConnection()app.createDatabaseStructure(connection)app.addDemoRecords(connection)connection.close()Vertx vertx = Vertx.vertx()vertx.deployVerticle(new Main())}
我们保留了生成数据库结构和创建示例记录的代码。在这些代码后面,我们创建一个Vertx实例,并使用方法deployVerticle来部署它。这将启动Vert.x系统,而这个系统将调用Verticle的方法start()。
现在应该可以运行这些代码了,为此可按Ctrl + F11(cmd + F11)。如果一切顺利,你将在控制台窗口中看到如下输出:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".SLF4J: Defaulting to no-operation (NOP) logger implementationSLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for furtherdetails.
由于我们没有在项目中添加Simple Logging Facade For Java(SLF4J)依赖,Vert.x系统不知道如何写入日志。我们完成可忽略这种警告,应用程序将继续运行,而不会有其他的输出。如果出现栈跟踪,请核查代码。现在启动你喜欢的浏览器,并访问如下URL:http://localhost:8080/blogs/。
你将在浏览器中看到示例博文的XML表示:

如果你修改URL,这个Web服务将返回一个404错误页面:

要停止这个Web服务,可在选项卡Console中单击工具栏上工具提示为“Terminate”的按钮:

