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

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

咱們開始第一步,用vert.x編寫一個簡單可行的wiki應用,而在下一步的迭代中,將會引入更多好的代碼庫,以及適當的測試,咱們能夠看到用Vert.x構建一個原型是一個簡單實在的目標。git

在這個階段,wiki應用將使用服務端渲染HTML頁面,數據庫持久層用的JDBC鏈接,咱們將使用下面的類庫來完成要作的。github

    1.Vert.x web做爲Vert.x核心庫用來建立HTTP服務,可是沒有提供優雅的apis處理路由,請求處理。web

    2.Vert.x JDBC client用來提供在JDBC基礎的異步API。sql

    3.Apache FreeMarker是用來渲染服務器端頁面的一個簡單模板引擎。數據庫

    4.Txtmark用來渲染Markdown到HTML,容許用Markdown編輯wiki頁面。編程

初始化一個maven項目api

這個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表,有如下字段:

典型的數據庫操做增刪改查,在開始以前,咱們理解一下在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)。

而後咱們看看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/

 

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

相關文章
相關標籤/搜索