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());
}
});
- ar是一个AsyncResult<Void>. AsyncResult<T>对象,用来传递异步的运行结果,可能响应的T是一个成功的或者在硬性中失败的异常。
数据库初始化:
wiki数据库包含一个简单的pages表,有以下字段:
典型的数据库操作增删改查,在开始之前,我们理解一下在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 = ?";
- 其中的问号是占位符,当实际执行的时候会把参数传进去,Vert.x JDBC client可以防止SQl注入
代码中的verticle会保持一个JDBCClient的引用(io.vertx.ext.jdbc包)获取数据库连接,在MainVerticle中有一个属性记录日志(org.slf4j)。
然后我们看看prepareDatabase方法的实现,有一个数据库连接,然后执行数据库操作创建一个Pages表(如果不存在的情况下)
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引入依赖(事实上他们来自容一个作者)
默认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服务。
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/
未完待续,好累,先休息了!