Vert.x 3.8.0 框架基础


Vert.x 3.8.0已经发布(写这篇文章的时候),本次改动说大不大,说小也不小,删了之前介绍Vert.x 3.5的文章,重新使用3.8版本开发一个单点后台程序,为Vert.x发展信徒(误)

开发环境:idea+maven+mysql+java8
计划开发一个图书馆信息管理系统。只做最简单的api后台功能,目的是梳理Vert.x开发思路。能力一般水平有限,从代码出发,尽量说清楚相关的内容。
前端实在太复杂了,不感动不感动

本篇文章为第一章,创建项目,介绍一下基础知识,并创建http server监听web端口。

建立项目

创建maven工程,pom文件添加Vert.x依赖

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-core</artifactId>
    <version>3.8.0</version>
</dependency>

然后创建对应的类
com.bobo.library.MainVerticle继承AbstractVerticle抽象类,再重写start()函数

package com.bobo.library;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;

public class MainVerticle extends AbstractVerticle {
    public void start(Promise<Void> startPromise) throws Exception {
        System.out.println("这里是 MainVerticle 的 start 方法");
    }
}

我这里使用com.bobo.library.MainVerticle类作为程序入口,start()函数就相当于往常程序的

public static void main(String[] args) { } 

start() 函数

start()函数是 Verticle接口中定义的函数,当我们创建的Verticle被部署,将会自动执行start()函数。
start()函数有3个重载

/**
  * If your verticle does a simple, synchronous start-up then override this method and put your start-up
  * code in here.
  * @throws Exception
  */
 public void start() throws Exception {
 }
   /**
  * Start the verticle.<p>
  * This is called by Vert.x when the verticle instance is deployed. Don't call it yourself.<p>
  * If your verticle does things in its startup which take some time then you can override this method
  * and call the startFuture some time later when start up is complete.
  * @param startFuture  a future which should be called when verticle start-up is complete.
  * @throws Exception
  */
 @Override
 public void start(Future<Void> startFuture) throws Exception {
   start();
   startFuture.complete();
 }

以及3.8.0版本新加入的

/**
 * Start the verticle instance.
 * <p>
 * Vert.x calls this method when deploying the instance. You do not call it yourself.
 * <p>
 * A promise is passed into the method, and when deployment is complete the verticle should either call
 * {@link io.vertx.core.Promise#complete} or {@link io.vertx.core.Promise#fail} the future.
 *
 * @param startPromise  the future
 */
default void start(Promise<Void> startPromise) throws Exception {
  start((Future<Void>) startPromise);
}

部署Verticle

写一个main方法测试部署一下

import com.bobo.library.MainVerticle;
import io.vertx.core.Vertx;

public class TestMain {
    public static void main(String[] args) {
        Vertx.vertx().deployVerticle(MainVerticle.class.getName());
    }
}

运行TestMain,创建Vertx对象,部署Verticle对象,然后调用deployVerticle()函数部署前面的MainVerticle。部署完成后自动执行start()函数。

什么是Verticle

大多文章一般把这一项放在开头进行说明,虽然解释的十分详尽,但对于第一次接触Vert.x的新手来说,理解起来还是挺困难的。其实Verticle就是运行在Vert.x里的一个代码模块。Vert.x 和spring ,netty等框架不同,Vert.x 不止是一个框架,更是一个应用平台。我们可以使用各种语言编写Vert.x应用(其实就是Verticle),发布在Vert.x平台实例上运行。

如果Vert.x比作TomCat,一个Verticle就是webapps目录下发布的一个项目

创建 HTTP server Verticle

Vert.x官方文档中已经详细介绍了如何创建httpserve,这里就不再啰嗦了。直接编写代码进行讲解。

  • 创建com.bobo.library.http.HTTPServerVerticle,同样继承AbstractVerticle类,重载start()函数。
  • 参考Vert.x官方文档创建HttpServer对象,使用requestHandler函数为HttpServer对象绑定处理函数,监听对应tcp端口。(lambda语法是java8的魂,以后有心情专门梳理一下)
package com.bobo.library.http;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;

public class HTTPServerVerticle extends AbstractVerticle {
    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        HttpServer server = vertx.createHttpServer();

        server.requestHandler(request -> {

            // This handler gets called for each request that arrives on the server
            HttpServerResponse response = request.response();
            response.putHeader("content-type", "text/plain");

            // Write to the response and end it
            response.end("Hello World!\n");
        });

        server.listen(8081);

    }
}
  • 发布HTTPServerVerticle 实例,在MainVerticlestart()函数中部署即可。
vertx.deployVerticle(HTTPServerVerticle.class.getName());
  • 启动TestMain测试http服务
$ curl http://127.0.0.1:8081 
Hello World!

至此一个简单的HttpServer监听就创建完成了。

路由 Router

上面示例的httpserver监听的是一个端口,所有到这个端口的请求都由requestHandler函数指定的Handler执行。一般我们正常的系统都有多个api接口。如果请求都发到同一个Handler再手动分发就太麻烦了。
Vert.x-web中提供Router接口,使用Router接收HTTP请求并找到该请求的第一个匹配路由,并将请求传递给该路由。

先在pom.xml中导入Vert.x-web模块的依赖

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web</artifactId>
    <version>3.8.0</version>
</dependency>

改造一下HTTPServerVerticle

 @Override
public void start(Promise<Void> startPromise) throws Exception {
    HttpServer server = vertx.createHttpServer();

    Router router = Router.router(vertx);
    router.route().handler(routingContext -> {
        HttpServerResponse response = routingContext.response();
        response.end("Hello World from router!\n");
    });

    server.requestHandler(router);

    server.listen(8081);
}

这样所有的请求会被router拦截,通过route()函数获取所有的请求,然后由指定handler处理。
当然,一个路由可以绑定多个handler,按照顺序逐个执行。

router.route().handler(routingContext -> {
    HttpServerResponse response = routingContext.response();
    System.out.println("Hello World ");
    routingContext.next();
});
router.route().handler(routingContext -> {
    HttpServerResponse response = routingContext.response();
    System.out.println("from router!");
    response.end("Hello World from router2!\n");
});

routingContext 中封装了关于这次http请求的所有信息。
其中routingContext.request()中是所有的用户请求数据,例如

  • routingContext.getBody()表示POST、PATCH、PUT请求传递的request参数;
  • routingContext.request();中包括getParam(String)获取参数,request.getHeader()获取请求头等等功能。

routingContext.response()是对用户请求的响应内容设置。
还可以使用order()函数设置handler()处理顺序。

router.route().order(1).handler(routingContext -> {
    HttpServerResponse response = routingContext.response();
    System.out.println("from router!");
    response.end("Hello World from router2!\n");
});
router.route().order(0).handler(routingContext -> {
    HttpServerResponse response = routingContext.response();
    System.out.println("Hello World ");
    routingContext.next();
});

这段代码和上面的一段完全等价。需要注意,一个http请求只能response.end()一次,所以需要保证在最后一个路由中返回数据。

前面的router.route()函数没有指定路由匹配条件。所有的请求都会由被匹配到。如果我们由多个api接口,就需要为路由配置多个匹配。

router.route("/test/books").handler(routingContext -> {
    System.out.println("请求books接口,返回所有的书籍信息");
    HttpServerResponse response = routingContext.response();
    response.putHeader("Content-type", "application/json").end("[]");
});
router.route("/test/books/:id").handler(routingContext -> {
    HttpServerRequest request = routingContext.request();
    String id = request.getParam("id");
    System.out.printf("请求book接口,返回id为 %s 的书籍", id);
    HttpServerResponse response = routingContext.response();
    response.putHeader("Content-type", "application/json").end("{}");
    routingContext.next();
});

上述代码表示为router添加两个路由,/test/books/test/books/:id,匹配相同的uri分别处理。:id为restful风格的浏览器参数。虽然匹配了uri,但是所有到这个uri的请求都会被处理,遵循restful风格的话,我们还需要将请求细化。比如到/test/books的请求,get表示获取所有书籍信息,post表示添加书籍信息;到/test/books/:id的请求是精确到一本书籍的请求,get表示获取这本书籍信息,delete删除书籍信息,put覆盖更新书籍信息,patch增量更新书籍信息。

router.get("/test/books").handler(routingContext -> {
    System.out.println("get方式请求books接口,返回所有的书籍信息");
    HttpServerResponse response = routingContext.response();
    response.putHeader("Content-type", "application/json").end("[]");
});
router.post("/test/books").handler(routingContext -> {
    String bodyAsString = routingContext.getBodyAsString();
    System.out.println("post方式请求books接口,表示添加书籍信息。请求的post body为:" + bodyAsString);
    HttpServerResponse response = routingContext.response();
    response.putHeader("Content-type", "application/json").end(bodyAsString);
});
router.get("/test/books/:id").handler(routingContext -> {
    HttpServerRequest request = routingContext.request();
    String id = request.getParam("id");
    System.out.printf("get方式请求book接口,返回id为 %s 的书籍", id);
    HttpServerResponse response = routingContext.response();
    response.putHeader("Content-type", "application/json").end("{}");
    routingContext.next();
});
router.delete("/test/books/:id").handler(routingContext -> {
    HttpServerRequest request = routingContext.request();
    String id = request.getParam("id");
    System.out.printf("delete方式请求book接口,删除id为 %s 的书籍", id);
    HttpServerResponse response = routingContext.response();
    response.putHeader("Content-type", "application/json").end("删除书籍:" + id);
    routingContext.next();
});
router.put("/test/books/:id").handler(routingContext -> {
    HttpServerRequest request = routingContext.request();
    String id = request.getParam("id");
    System.out.printf("put方式请求book接口,更新id为 %s 的书籍", id);
    HttpServerResponse response = routingContext.response();
    JsonObject jsonObject = new JsonObject().put("id", id);
    response.putHeader("Content-type", "application/json").end(jsonObject.toString());
    routingContext.next();
});
router.patch("/test/books/:id").handler(routingContext -> {
    HttpServerRequest request = routingContext.request();
    String id = request.getParam("id");
    System.out.printf("patch方式请求book接口,增量id为 %s 的书籍", id);
    HttpServerResponse response = routingContext.response();
    JsonObject jsonObject = new JsonObject().put("id", id);
    response.putHeader("Content-type", "application/json").end(jsonObject.toString());
    routingContext.next();
});

使用restful风格api,所有的uri实现内容一目了然。推荐阅读https://restfulapi.net/
lambda 语法能提高编码和部分执行效率,某种意义上代码也会更加明了。

本文代码链接

github

以下为吐槽,看到这里就关了吧

前段时间身体不太好,别人生病都是掉血掉蓝,为啥我感觉自己生病还掉智力呢。。。而且体重也上去了。博客一年多一段时间没有更新了,攒了一部分文章会陆续发出来,顺便换个主题换换心情。
前段时间Vert.x发布3.8.0,这次版本迭代改动还是挺大的,不少我以为会在4.0版本发布的内容被提前发布出来了,于是我获得了下面的惊喜。。。
WechatIMG448

整个项目都在warning,几百个模块代码都需要重组。。。。
不过3.8.0毕竟还是小版本,比较根本的东西并没有改动,加加班也就搞定了。


文章作者: 鱍鱍
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 鱍鱍 !
 上一篇
Vert.x 异步调用基础 Vert.x 异步调用基础
上篇文章 简单介绍了一下如何使用Vert.x部署一个Verticle并创建一个http server。以及路由的试用。在部署Verticle的时候,我们使用的 start() 函数的重载是start(Promise<Void>)
下一篇 
star UML 3 license star UML 3 license
前段时间换电脑,之前的部分软件需要重新激活。这里总结一下用到的软件的激活方式,以备不时之需。如果有能力还请支持一下开发者。 star UML 3 是一个优秀的开源uml工具,是软件设计不可少的工具,但是价格实在是劝退门槛。以前记录的激活方式
2018-06-10
  目录