Vert.x示例demo解析 译(二)

TIP相应的源代码在step-1中(https://github.com/vert-x3/vertx-guide-for-java-devs)

我们开始第一步,用vert.x编写一个简单可行的wiki应用,而在下一步的迭代中,将会引入更多好的代码库,以及适当的测试,我们可以看到用Vert.x构建一个原型是一个简单实在的目标。

在这个阶段,wiki应用将使用服务端渲染HTML页面,数据库持久层用的JDBC连接,我们将使用下面的类库来完成要做的。

    1.Vert.x web作为Vert.x核心库用来创建HTTP服务,但是没有提供优雅的apis处理路由,请求处理。

    2.Vert.x JDBC client用来提供在JDBC基础的异步API。

    3.Apache FreeMarker是用来渲染服务器端页面的一个简单模板引擎。

    4.Txtmark用来渲染Markdown到HTML,允许用Markdown编辑wiki页面。

初始化一个maven项目

这个guide选择用Apache Maven作为构建工具,主要是因为它很好地集成了主要的开发环境,你也可以使用其他的构建工具,比如Gradle。

Vert.x社区提供了模板项目结构(https://github.com/vert-x3/vertx-maven-starter ),你可以选择合适的clone下来。如果你偏向于使用Git作为版本控制,最快的选择是clone那个repository,然后删除其中的 .git/文件夹,然后在其中创建一个新的Git仓库

git clone https://github.com/vert-x3/vertx-maven-starter.git vertx-wiki
cd vertx-wiki
rm -rf .git
git init

这个项目提供一个简单的verticle,以及一个单元测试。你可以安全地删除wiki项目下src/文件夹下的java文件,但是在需要项目构建,还有可以跑起来。

mvn package exec:java

你可以注意到maven项目中pom.xml做了两件集成的事:

    1.使用Maven Shade Plugin来打包一个拥有全部依赖的jar包,带有 -fat.jar后缀,所以我们可以称之“fat jar”

    2.使用Exec Maven Plugin提供exec:java命令,这样可以通过vert.x的io.vertx.core.Launcher类来启动项目,这实际上算是分布式Vert.x的命令行工具。

最后,你会发现的redeploy.sh和redeploy.bat脚本的存在,你可以选择使用自动编译和部署的更改的代码。注意这么做,需要确定脚本中的VERTICLE变量和使用的主verticle匹配。

NOTE:

另外,Fabric8项目有一个Vert.x Maven plugin,有初始化,构建,打包和运行vert.x的命令。

也可以类似的通过clone git仓库来创建一个项目

mkdir vertx-wiki
cd vertx-wiki
mvn io.fabric8:vertx-maven-plugin:1.0.7:setup -DvertxVersion=3.5.0
git init

添加需要的依赖

在pom.xml中添加web处理和渲染的依赖:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web</artifactId>
</dependency>
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-templ-freemarker</artifactId>
</dependency>
<dependency>
  <groupId>com.github.rjeschke</groupId>
  <artifactId>txtmark</artifactId>
  <version>0.13</version>
</dependency>

TIP

顾名思义,vertx-web-templ-freemarker就和你想的一样,Vert.x web提供了对流行的模板引擎的支持:Handlebars, Jade, MVEL, Pebble, Thymeleaf。

第二波要添加JDBC需要的依赖:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-jdbc-client</artifactId>
</dependency>
<dependency>
  <groupId>org.hsqldb</groupId>
  <artifactId>hsqldb</artifactId>
  <version>2.3.4</version>
</dependency>

Vert.x JDBC client库提供了任何兼容的JDBC数据库,当然我们在路径中需要设置JDBC驱动。

HSQLDB是一个用java编写的内嵌数据库,当使用内嵌数据库避免第三方数据库依赖是比较流行的选择,在单元测试和集成测试中提供内存数据库也很流行。

NOTE

vert.x也直接提供了MySQL and PostgreSQL client 库。

当然你也可以使用常规的Vext.x JDBC client连接Mysql或者PostgreSQL数据库,但是那两个类库提供更好的性能处理服务器网络协议,比起阻塞的JDBC APIs。

NOTE

vert.x也提供了类库处理流行的非关系型数据库MongoDB和Redis。强大的社区提供其他存储系统的支持,比如Apache Cassandra, OrientDB or ElasticSearch。

解剖verticle

我们的wiki项目的verticle包含一个io.vertx.guides.wiki.MainVerticle类,这个类继承了io.vertx.core.AbstractVerticle,基本的verticles提供了如下几点:

    1.重写生命周期的 start和stop方法

    2.一个protected字段指向verticle在Vert.x中的部署的环境

    3.一个可以访问verticle配置文件和传递外部配置文件的访问器。

我们开始我们的verticle,通过下面覆盖 start方法

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start(Future<Void> startFuture) throws Exception {
    startFuture.complete();
  }
}

这里的start和stop方法有两种形式,一种没有参数,一种有Future参数。没有参数的方法意味着verticle初始化和house-keeping在没有抛出异常的情况都会成功;带有Future参数的方法提供了更细粒度的方法,指出操作成功还是失败。事实上,一些初始化或者清除的代码需要异步的操作,因此需要future对象来匹配异步结果。

vert.x的Future对象和回调

Vert.x的Futures不是JDK的futures:他们可以以非阻塞的方式查询和调用,他们可以应用于异步任务调度中,尤其在verticles 发布和检查他们是否发布成功。

Vert.x的核心APIs是在回调和异步事件的通知的基础上写的。经验丰富的开发者很自然地会认为,这样打开了“callback hell”的大门,当有多次嵌套的时候,那时候代码真是令人难以理解,通过下面这个虚构的代码来说明:

foo.a(1, res1 -> {
  if (res1.succeeded()) {
    bar.b("abc", 1, res2 -> {
      if (res.succeeded()) {
         baz.c(res3 -> {
           dosomething(res1, res2, res3, res4 -> {
               // (...)
           });
         });
      }
    });
  }
});

而核心APIs的设计要高瞻远瞩,当回调的使用允许不同的抽象调用就会变得有趣,Vert.x是一个大的开放的项目,回调可以用不同的模块实现,这样更好地应对异步编程:响应式的拓展,使用字节码等。

因为Vert.x的APIs是以回调为主的,这个guide在第一节使用回调来使读者熟悉Vert.x的核心概念。对于使用异步是非常简单的,可以在不同的章节的异步代码之间连成一线,在示例代码中,但是有些回调并不是很好理解。所以我们介绍RxJava来支持用异步代码更好的处理事件流。

Wiki verticle初始化阶段

为了让我们的wiki运行起来,我们需要执行2个阶段的初始化:

    1.我们需要建立一个JDBC连接,也要确定数据库运行是正常的。

    2.我们需要为我们的web应用启动一个HTTP服务。

每一个阶段都可能失败(eg.,HTTP服务TCP端口已经占用),这样应用就不会运行,他们需要连接数据库。

为了使代码变得简洁,我们将在每一层定义一个方法,我们在每一个方法返回的时候,采用返回一个Future的形式,无论方法是成功还是失败:

private Future<Void> prepareDatabase() {
  Future<Void> future = Future.future();
  // (...)
  return future;
}

private Future<Void> startHttpServer() {
  Future<Void> future = Future.future();
  // (...)
  return future;
}

通过每一个方法返回一个Future对象,那么就可以组成了Strat方法的实现:

@Override
public void start(Future<Void> startFuture) throws Exception {
  Future<Void> steps = prepareDatabase().compose(v -> startHttpServer());
  steps.setHandler(startFuture.completer());
}

当prepareDatabase的Future返回成功,然后在执行startHttpServer方法返回成功后,steps后续方法继续执行。如果prepareDatabase方法返回失败,那么startHttpServer将不执行,然后steps的Future将是在一个failed的状态,并且将抛出带有那个异常信息的异常。

最后steps完成使命:setHandler定义一个Handler在完成的时候调用。在我们的例子里,我们就想简单地完成steps的startFuture,然后用complete方法操作一个Handler,下面是实现:

Future<Void> steps = prepareDatabase().compose(v -> startHttpServer());
steps.setHandler(ar -> {  (1)
  if (ar.succeeded()) {
    startFuture.complete();
  } else {
    startFuture.fail(ar.cause());
  }
});
  1. ar是一个AsyncResult<Void>. AsyncResult<T>对象,用来传递异步的运行结果,可能响应的T是一个成功的或者在硬性中失败的异常。

数据库初始化:

wiki数据库包含一个简单的pages表,有以下字段:

112703_sOqH_2277632.png

典型的数据库操作增删改查,在开始之前,我们理解一下在MainVerticle类中的静态字段。注意到这些字段写了HSQLDB数据库理解的SQL语句,但是可能其他的关系型数据库会补能执行。

private static final String SQL_CREATE_PAGES_TABLE = "create table if not exists Pages (Id integer identity primary key, Name varchar(255) unique, Content clob)";
private static final String SQL_GET_PAGE = "select Id, Content from Pages where Name = ?"; (1)
private static final String SQL_CREATE_PAGE = "insert into Pages values (NULL, ?, ?)";
private static final String SQL_SAVE_PAGE = "update Pages set Content = ? where Id = ?";
private static final String SQL_ALL_PAGES = "select Name from Pages";
private static final String SQL_DELETE_PAGE = "delete from Pages where Id = ?";
  1. 其中的问号是占位符,当实际执行的时候会把参数传进去,Vert.x JDBC client可以防止SQl注入

代码中的verticle会保持一个JDBCClient的引用(io.vertx.ext.jdbc包)获取数据库连接,在MainVerticle中有一个属性记录日志(org.slf4j)。

171642_aoQy_2277632.png

然后我们看看prepareDatabase方法的实现,有一个数据库连接,然后执行数据库操作创建一个Pages表(如果不存在的情况下)

172027_q1mi_2277632.png

    1.createShared创建一个共享的vertx实例verticles连接,或许是个不错的主义。

    2.配置JDBC的URL。

    3.配置数据库连接的drive_class。

    4.max_pool_size用来配置数据并发连接数,我们在这里选择30,这里只是一个随意的选择。

    5.获取数据库连接,这是一个异步的操作,返回AsyncResult<SQLConnection>,所以需要测试数据库连接是否真的被建立。

    6.如果未能获取数据库连接,异步结果AsyncResult将返回一个造成这个原因的异常。

    7.SQLConnection是一个AsyResult返回成功后的结果,我们可以使用她来执行SQL语句。

    8.在检查SQL查询结果是否成功之前,我们要释放连接,否则连接池会被耗尽。

    9.完成Future对象的调用成功信息。

TIP:

Vert.x项目支持的SQL数据库模型在SQL语句上没有更多的支持,而是将重点放在异步访问数据库上,然而,这并不限制使用社区中更前卫的数据库模块。我们也可以去关注为vert.x提供支持的jOOq generator fo,或者 POJO mapper。

关于日志:

前面说到记录日志,我们选择了SFL4J库,vert.x在日志方面不是一个顽固,基本可以支持大量流行的日志类库方案。我们选择了SLF4J是因为java生态里面流行的日志系统和比较统一的库。

我们也推荐使用Logback作为日志的实现,集成SLF4J和Logbck可以通过添加两个依赖实现,或者仅仅使用logback-classic引入依赖(事实上他们来自容一个作者)

211450_7T6x_2277632.png

默认SLF4J会在控制台输出大量的Vert.x, Netty, C3PO 和wiki应用日志,我们可以通过增加一个配置减少冗余的内容(src/main/resources/logback.xml),更多的细节可以看 https://logback.qos.ch/

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="com.mchange.v2" level="warn"/>
  <logger name="io.netty" level="warn"/>
  <logger name="io.vertx" level="info"/>
  <logger name="io.vertx.guides.wiki" level="debug"/>

  <root level="debug">
    <appender-ref ref="STDOUT"/>
  </root>

</configuration>

内嵌的HSQLDB与日志并不是集成的很好,默认它会使用调用系统的日志,我们可以通过设置-Dhsqldb.reconfig_logging=false使JVM在运行程序时不调用。

HTTP服务初始化:

vert.x-web项目可以容易得定义对HTTP请求进行路由,事实上,Vert.x核心APIs语序启动HTTP服务并且监听建立的连接,但是没有提供其他的工具,不过由不同的根据请求的URL或者处理请求的handlers。router的角色就是把请求分派给不同的处理进程。

初始化过程包括建立一个equest router,和启动一个HTTP服务

212842_fD12_2277632.png

    1.Vert.x context提供方法创建HTTP servers, clients, TCP/UDP servers and clients等。

    2.构建Router(vertx-web: io.vertx.ext.web.Router)

    3.Routers拥有自己的handler,可以通过URL和/或HTTP方法定义。短程序java lambda是一个选择,但是对于更详细的处理程序来说,引用私有方法是一个好主意。注意,URL可以是参数化的,/wiki/:page匹配像/wiki/Hello的页面,在这种情况下,page代表的参数就是Hello。

    4.所有HTTP POST请求都通过第一个handler(io.vertx.ext.web.handler.BodyHandler),这个处理程序会解析HTTP请求(比如表单提交),可以用来处理Vert.xd的缓存对象。

    5.router对象可以用作HTTP服务器处理程序,然后将其分派给如上所定义的其他处理程序。

    6.启动一个HTTP服务是一个异步操作,需要检测AsyncResult<HttpServer>返回的结果是否成功,顺便说一下服务器启动占用TCP的8080接口。

 

原文链接:http://vertx.io/docs/guide-for-java-devs/

103728_2T6n_2277632.png

 

未完待续,好累,先休息了!

220106_jHy6_2277632.png

 

 

 

 

转载于:https://my.oschina.net/u/2277632/blog/1577605

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值