Vert.x3 Core手冊簡譯(Java版)[Part 1. 截止Buffer 一節(jié)]

本文依照 知識共享許可協(xié)議(署名-非商業(yè)性使用-禁止演繹) 發(fā)布。
感謝@嚴(yán)禁扯淡 的修改建議推溃。

2017-2-9:更新異步協(xié)同部分昂利。

先放兩個鏈接:
源文檔
Github repository.

話說用Java這么多年,沒給社區(qū)做過什么貢獻铁坎。這次趁使用Vert.x3的機會蜂奸,簡單翻譯了核心包的手冊。

Vert.x3的手冊簡潔明了硬萍,過一遍即可輕松入門扩所。所以諸君若是看到什么無法理解的,必定是我的譯文有問題(嘿嘿朴乖,水平低祖屏,見諒)助赞。


部分名詞對照表:

  • handler:事件處理器
  • event loop:事件循環(huán)(線程)
  • verticle:Vert.x的專有名詞。指代Vert.x中基本的功能單元袁勺,一個Vert.x應(yīng)用應(yīng)該是由一組verticles構(gòu)成的雹食。
  • worker:顧名思義,干活的(線程)期丰。對應(yīng)文檔中有worker thread pool群叶,worker verticle(Vert.x里的work verticle與標(biāo)準(zhǔn)版verticle區(qū)別較大)。
  • event bus:事件總線

Vert.x核心包提供如下的功能:

  • TCP客戶端與服務(wù)器
  • HTTP客戶端與服務(wù)器(包含Websocket支持)
  • 事件總線(Event bus)
  • 共享數(shù)據(jù)-局部的map和集群下的分布式map
  • 定時或延遲的處理
  • 部署咐汞、卸載verticle
  • 數(shù)據(jù)報文套接字(datagram socket)
  • DNS客戶端
  • 文件系統(tǒng)存取
  • 高可用性
  • 集群

核心包提供的功能是相當(dāng)?shù)讓拥摹_@意味著沒有諸如數(shù)據(jù)庫存取化撕、認(rèn)證几晤、高級web功能此類的組件,你可以在Vert.x ext(擴展包)找到以上這些植阴。

Vert.x 的核心小且輕量蟹瘾,諸位可以各取所需。它可以整個的嵌入你現(xiàn)有的應(yīng)用當(dāng)中掠手,不需要為了使用Vert.x而以特別的方式組織你的應(yīng)用憾朴。

你可以在任何Vert.x支持的語言中使用核心包。但是有一點要提一下喷鸽,我們不會迫使你在Javascript或者Ruby里使用為Java準(zhǔn)備的API众雷;畢竟不同的語言有不同的約定和慣用法,強迫Ruby開發(fā)者使用Java的慣用法確實比較古怪做祝。相反的砾省,我們?yōu)槊恳环N語言都生成了等價于核心Java API的慣用法(idiomatic)

現(xiàn)在開始混槐,我們將使用核心包(core)指代Vert.x core编兄。

如果你使用Maven或Gradle,把下列幾行加入項目描述符的依賴配置即可使用核心包的API:

  • Maven (in your pom.xml):
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-core</artifactId>
    <version>3.2.1</version>
</dependency>
  • Gradle (in your build.gradle file):
    compile io.vertx:vertx-core:3.2.1

下面讓我們看看核心包里的各種特性声登。

開始使用Vert.x

注意:這里所述大部分都是Java適用的狠鸳,語言相關(guān)的部分需要以某種方式進行轉(zhuǎn)換。

在Vert.x里悯嗓,如果沒有Vertx對象件舵,那幾乎什么都做不了!
Vertex對象是Vert.x的控制中心脯厨,許多功能都通過它實現(xiàn)芦圾。包括創(chuàng)建客戶端和服務(wù)器、獲取事件總線(event bus)的引用俄认、設(shè)置定時器,等等。

那么狂塘,如何獲取它的實例呢懒震?

如果你在程序中嵌入Vert.x,那么可以像下面這樣創(chuàng)建實例:
Vertx vertx = Vertx.vertx();

當(dāng)你使用Verticle時

注意:絕大部分應(yīng)用其實只需要一個Vert.x實例岂贩;當(dāng)然,你也可以創(chuàng)建多個實例萎津。例如卸伞,兩個總線需要隔離時或客戶端服務(wù)器需分組時。

創(chuàng)建Vert.x實例時指定可選項(option)

創(chuàng)建Vert.x實例時锉屈,如果缺省選項不合適荤傲,你也可以設(shè)定一些值:
Vertx vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(40));

VertxOptions對象有很多設(shè)置項,你可以配置集群(clustering)颈渊、高可用性(high availability)遂黍,worker 線程池的大小(pool sizes)等等俊嗽。詳細的內(nèi)容請參見Javadoc雾家。

創(chuàng)建集群模式(clustered)的Vert.x對象

如果你在使用Vert.x的集群模式(更多細節(jié)請參考下面的event bus一節(jié),關(guān)于集群下的event bus)绍豁,記得創(chuàng)建Vert.x對象也是異步的芯咧。

為了把集群里不同的vertx實例組織在一起,通常需要花一點時間(可能是幾秒鐘)竹揍。在這段時間里敬飒,為了不阻塞調(diào)用線程(the calling thread),結(jié)果會以異步的方式返回鬼佣。


Are you fluent驶拱?(fluent狂人,別走)

你可能已經(jīng)注意到晶衷,在前面的例子中蓝纲,我們使用了流式(fluent)的API。

流式API是指多個方法可以用鏈?zhǔn)降姆绞揭黄鹫{(diào)用晌纫。例如:
request.response().putHeader("Content-Type", "text/plain").write("some text").end();

這在Vert.x的API里是很普遍的模式税迷,你要試著習(xí)慣它。 :)

鏈?zhǔn)秸{(diào)用允許你更簡潔的編寫代碼锹漱。當(dāng)然箭养,如果你不喜歡這種方式,這也不是必須的哥牍。你可以愉快地忽略這些毕泌,然后像下面這樣寫:

HttpServerResponse response = request.response();
response.putHeader("Content-Type", "text/plain");
response.write("some text");
response.end();

不要調(diào)用我們喝检,我們會調(diào)用你(Don’t call us, we’ll call you.)

大部分Vert.x API 都是事件驅(qū)動的。這意味著如果你對Vert.x里發(fā)生的某事感興趣撼泛,Vert.x會以向你發(fā)送事件(events)的方式通知你挠说。

例如下面的事件:

  • 定時器被觸發(fā)
  • socket收到了一些數(shù)據(jù)
  • 一些數(shù)據(jù)已經(jīng)從磁盤上被讀取
  • 某個異常產(chǎn)生了
  • HTTP服務(wù)器接受了一個請求

通過提供handlers,你可以處理這些事件愿题。例如定義一個定時器事件:

vertx.setPeriodic(1000, id -> {
  // This handler will get called every second
  System.out.println("timer fired!");
});

或者接受一個HTTP請求:

server.requestHandler(request -> {
  // This handler will be called every time an HTTP request is received at the server
  request.response().end("hello world!");
});

如果觸發(fā)了某個事件损俭,Vert.x將會異步地(asynchronously)調(diào)用它(the handler)。

這里我們發(fā)現(xiàn)了Vert.x的如下重要概念潘酗。


不要阻塞我8吮(Don't block me!)

除了極少的例外(即某些以‘Sync’結(jié)尾的文件系統(tǒng)操作),Vert.x里沒有API會阻塞調(diào)用線程仔夺。

如果結(jié)果可以即刻獲得琐脏,它會立刻被返回。否則囚灼,通常你需要提供一個處理器骆膝,以便稍后接收事件。

沒有API會阻塞線程意味著:用少量的線程灶体,就可以處理大量的并發(fā)阅签。

傳統(tǒng)的阻塞API可能會在哪些地方發(fā)生呢:

  • 從socket讀取數(shù)據(jù)
  • 寫數(shù)據(jù)到磁盤
  • 發(fā)消息給某個接收者,然后等待回應(yīng)
  • 蝎抽。政钟。很多其他狀況

在上面這些案例中,你的線程在等待一個結(jié)果時不能做其他任何事樟结,這樣是很低效的养交。

這也意味著,如果你想使用阻塞API處理大量并發(fā)瓢宦,你將需要大量的線程來防止你的應(yīng)用卡住碎连。

線程在內(nèi)存(例如:棧)和上下文切換方面的開銷不容忽視。

以很多現(xiàn)代的應(yīng)用所需求的并發(fā)級別驮履,阻塞的方式根本實現(xiàn)不了鱼辙。


Reactor and Multi-Reactor(反應(yīng)器和多路反應(yīng)器?)

前面我們提到了Vert.x的API是事件驅(qū)動的玫镐,當(dāng)handlers可用的時候倒戏,Vert.x會向它們傳遞事件。

絕大多數(shù)情況下恐似,Vert.x通過一個叫event loop的線程調(diào)用你的handlers杜跷。

event loop可以在事件到達時持續(xù)不斷地將其分發(fā)給不同的handler,因為Vert.x和你的應(yīng)用中不會有什么是阻塞的。

同樣葛闷,因為沒什么是阻塞的憋槐,所以event loop具有在短時間內(nèi)分發(fā)巨量事件的潛力。例如孵运,單個event loop可以極迅速地處理數(shù)千的HTTP請求秦陋。

我們稱之為Reactor模式

你之前可能已經(jīng)聽說過它--nodejs就實現(xiàn)了這種模式治笨。

標(biāo)準(zhǔn)的Reactor實現(xiàn)里,有一個單獨的event loop(single event loop)線程赤嚼,它會在所有事件到達時持續(xù)不斷地將其分發(fā)給所有的handler旷赖。

單一線程的困擾在于,在任意時刻更卒,它只能在一個cpu核心上運行等孵。所以如果你希望你的單線程Reactor應(yīng)用(或者Nodejs應(yīng)用)能夠運用上多核服務(wù)器的擴展能力( scale over your multi-core server ),你需要啟動多個進程并管理好它們蹂空。

Vert.x的工作方式不同于此俯萌,每個vertx實例會維護數(shù)個event loop(several event loops)。缺省情況下上枕,我們基于機器上可用的核心數(shù)來確定這個數(shù)字咐熙,當(dāng)然這個也可以設(shè)置。

不像Nodejs辨萍,這意味著單個Vert.x進程可以利用到服務(wù)器的擴展棋恼。

為了與單線程的reactor模式區(qū)分開,我們稱之為Multi-Reactor模式锈玉。

注意:雖然一個Vert.x實例會維護多個event loop爪飘,但任何特定的handler都絕不會被并發(fā)地執(zhí)行,在絕大多數(shù)情況下(worker verticle除外),它都會被某個固定的event loop(exact same event loop)調(diào)用拉背。


黃金準(zhǔn)則:不要阻塞Event Loop(Don’t Block the Event Loop)

我們已經(jīng)了解了师崎,Vert.x的API是非阻塞的,不會阻塞event loop椅棺;但是犁罩,如果你在handler中自己(yourself)阻塞了event loop,那么上面的其實都沒啥用土陪。昼汗。

如果你這么干了,那么event loop被阻塞的時候它啥都干不了鬼雀。再如果你阻塞了Vert.x實例里所有的event loop顷窒,那你的應(yīng)用將會陷入完全停滯的狀態(tài)!

所以千萬別這么干!我們已經(jīng)警告過你了哈(You have been warned)鞋吉。

阻塞的例子包括:

  • Thread.sleep()
  • 等待一個鎖
  • 等待一個同步鎖或監(jiān)視器(例如同步塊(synchronized section))
  • 做一個耗時的數(shù)據(jù)庫操作并等待結(jié)果
  • 做一個復(fù)雜的計算鸦做,耗費大量的時間
  • 在循環(huán)中(Spinning in a loop)

如果上面任何一步掛住了event loop,讓它花了大量時間( significant amount of time),那么你只能安心等待程序執(zhí)行谓着。

那么泼诱,什么樣是大量時間( significant amount of time)呢?

這個時間赊锚,實際上取決于你的應(yīng)用對并發(fā)量的需求治筒。

如果你有單一的event loop并且想每秒處理一萬個http請求,那么很明顯舷蒲,處理每個請求的時間不能超過0.1毫秒耸袜,所以阻塞不能超過這個時間。

這里面的計算不難牲平,作為練習(xí)堤框,我們將之留給讀者(The maths is not hard and shall be left as an exercise for the reader)。

如果你的應(yīng)用沒有響應(yīng)了纵柿,這可能是event loop被阻塞的信號蜈抓。為了幫助診斷這樣的問題,Vert.x會在檢測到某個event loop一段時間后還未返回時自動打印警告日志昂儒。如果在你的日志中看見這樣的警告沟使,那你可得調(diào)查調(diào)查了。
Thread vertx-eventloop-thread-3 has been blocked for 20458 ms

為了精確定位阻塞發(fā)生在何處荆忍,Vert.x也會提供堆棧跟蹤消息格带。

如果你想關(guān)閉這些警告或改變設(shè)置,可以在創(chuàng)建Vertx對象前刹枉,去VertxOptions對象里設(shè)置叽唱。


執(zhí)行阻塞式代碼

完美的世界里,不會有戰(zhàn)爭微宝,也不會有饑餓棺亭。所有的API都會以異步的方式寫成,小兔和小羊羔會手牽手地穿過陽光明媚的綠草地蟋软。

但是镶摘,現(xiàn)實世界不是這樣的。岳守。(你有關(guān)注最近的新聞嗎凄敢?)
(迷之聲:這篇文檔生成時發(fā)生了啥?湿痢?)

事實上涝缝,不算其他大多數(shù)庫扑庞,單單在JVM的子系統(tǒng)里就有同步的API和很多可能造成阻塞的方法。一個好的例子是JDBC拒逮,它天然就是同步的罐氨;無論怎么使勁,Vert.x也不會魔法滩援,沒辦法撒一點魔力粉就讓它變成異步的栅隐。

我們不會整夜不睡的去重寫所有(現(xiàn)存的組件)使它們成為異步的蝎亚,所以我們需要提供一種方式渗蟹,以使在Vert.x應(yīng)用里可以安全的使用“傳統(tǒng)的”阻塞API。

就像前面討論的逻锐,為了不妨礙event loop干其他有益的活佣赖,不能從它這直接調(diào)用阻塞操作恰矩。所以你該怎么辦呢?

指定待執(zhí)行的阻塞代碼和一個結(jié)果處理器(result handler)憎蛤,然后調(diào)用executeBlocking,當(dāng)阻塞代碼執(zhí)行完畢的時候纪吮,handler將會以異步的方式被回調(diào)(to be called back asynchronous )俩檬。

vertx.executeBlocking(future -> {
  // Call some blocking API that takes a significant amount of time to return
  String result = someAPI.blockingMethod("hello");
  future.complete(result);
}, res -> {
  System.out.println("The result is: " + res.result());
});

缺省情況下,如果executeBlocking 在同一上下文環(huán)境中(例如同一個verticle)被多次調(diào)用碾盟,那么不同的executeBlocking 會被順序地執(zhí)行(即一個接一個)棚辽。

如果你不在意executeBlocking 執(zhí)行的順序,那么你可以將ordered參數(shù)設(shè)置為false冰肴。這種情況下屈藐,worker pool有可能會并行地執(zhí)行executeBlocking。

另一種執(zhí)行阻塞代碼的方法是在worker verticle中干這事熙尉。

worker verticle總是由worker pool里的線程來執(zhí)行联逻。


異步協(xié)同

多個異步結(jié)果的協(xié)同可以由Vert.x的futures來實現(xiàn)。

CompositeFuture.all接受數(shù)個future參數(shù)(到6為止)并返回一個future检痰;當(dāng)所有的future都成功了包归,就返回成功(succeeded)的future,否則返回失敗(failed)的future:

Future<HttpServer> httpServerFuture = Future.future();
httpServer.listen(httpServerFuture.completer());

Future<NetServer> netServerFuture = Future.future();
netServer.listen(netServerFuture.completer());

CompositeFuture.all(httpServerFuture, netServerFuture).setHandler(ar -> {
  if (ar.succeeded()) {
    // All server started
  } else {
    // At least one server failed
  }
});

completer返回的handler會完成這個future铅歼。

CompositeFuture.any接受數(shù)個future參數(shù)(到6為止)并返回一個future公壤;只要有一個future成功了,那返回的future也成功(succeeded)椎椰,否則就失敗(failed)

Future<String> future1 = Future.future();
Future<String> future2 = Future.future();
CompositeFuture.any(future1, future2).setHandler(ar -> {
  if (ar.succeeded()) {
    // At least one is succeeded
  } else {
    // All failed
  }
});

2017-2-9 更新

新版CompositeFuture 的API 中增加了與all類似的系列方法:join 厦幅。
看文檔的說明,alljoin 的區(qū)別在于:

如果參數(shù)列表中的某個Future 失敗了慨飘,那么all 不會繼續(xù)等确憨,這個CompositeFuture將被標(biāo)記為失敗并完成;而join會繼續(xù)等待直到所有參數(shù)完成(不管成功與否)。


compose可以用來鏈?zhǔn)秸{(diào)用future:

FileSystem fs = vertx.fileSystem();

Future<Void> fut1 = Future.future();
Future<Void> fut2 = Future.future();

fs.createFile("/foo", fut1.completer());
fut1.compose(v -> {
  fs.writeFile("/foo", Buffer.buffer(), fut2.completer());
}, fut2);
fut2.compose(v -> {
  fs.move("/foo", "/bar", startFuture.completer());
}, startFuture);

Verticles

Vert.x有一個簡單的缚态、可擴展的磁椒,類似actor的部署方式(actor-like deployment )和開箱即用的并發(fā)模型,這方面可以節(jié)省下你親自動手的時間精力玫芦。

這個模型是完全可選的浆熔,如果你不想,Vert.x并不會強迫你以這種方式創(chuàng)建自己的應(yīng)用桥帆。医增。

這個模型并未嚴(yán)格地實現(xiàn)actor模型,但確實與其有相似之處老虫,尤其在并發(fā)性叶骨、擴展,部署方面祈匙。

為了使用這個模型忽刽,你需要將代碼寫成verticles的集合。

verticles是由Vert.x部署和運行的代碼塊夺欲。verticles可以由任何Vert.x支持的語言寫成跪帝,并且單獨的應(yīng)用可以包含多種語言寫就的verticles。

你可以把verticle看成有點像Actor Model里的actor些阅。

一個典型的應(yīng)用由同一時間運行在同一Vert.x實例里的很多verticle實例組成的伞剑。不同的verticle實例之間通過在event bus上向彼此發(fā)送消息來通信。

編寫verticle

verticle類必須實現(xiàn)Verticle接口市埋。

你可以直接實現(xiàn)這個接口黎泣,但通常有個更簡單的辦法,就是繼承下面這個抽象類:AbstractVerticle缤谎。

舉個例子:

public class MyVerticle extends AbstractVerticle {

  // Called when verticle is deployed
  public void start() {
  }

  // Optional - called when verticle is undeployed
  public void stop() {
  }

}

通常你需要像上面的例子一樣抒倚,重載start方法。

Vert.x部署verticle時弓千,會調(diào)用其start方法衡便。當(dāng)該start方法完成時,就認(rèn)為該verticle已啟動洋访。

你也可以選擇重載stop方法镣陕,當(dāng)verticle被卸載(undeployed)時會調(diào)用這個方法。同樣姻政,stop方法完成時呆抑,會認(rèn)為verticle已被終止。

異步verticle的啟動和終止

有時候你可能想在verticle啟動時做點耗時的事汁展,除非完成了鹊碍,否則不應(yīng)該認(rèn)定verticle已成功部署厌殉。比如你可能想在start方法里部署其他的verticles。

你不能在start方法里阻塞地等待其他verticles部署完成侈咕,這會打破我們的黃金準(zhǔn)則公罕。

那該怎么做呢?

合適的途徑是實現(xiàn)異步的start方法耀销。這個版本的start方法有一個future參數(shù)楼眷,這個方法返回時verticle并不會被認(rèn)定已經(jīng)部署完成。

等干完所有活之后(例如啟動其他的verticles)你就可以調(diào)用future對象的complete(或者fail)方法熊尉;這是一個信號罐柳,標(biāo)記你這里已經(jīng)都完成了。

下面有個例子:

public class MyVerticle extends AbstractVerticle {

  public void start(Future<Void> startFuture) {
    // Now deploy some other verticle:

    vertx.deployVerticle("com.foo.OtherVerticle", res -> {
      if (res.succeeded()) {
        startFuture.complete();
      } else {
        startFuture.fail();
      }
    });
  }
}

類似的狰住,stop方法也有一個異步的版本张吉。如果你做清理工作要花點時間,就可以用它催植。

public class MyVerticle extends AbstractVerticle {

  public void start() {
    // Do something
  }

  public void stop(Future<Void> stopFuture) {
    obj.doSomethingThatTakesTime(res -> {
      if (res.succeeded()) {
        stopFuture.complete();
      } else {
        stopFuture.fail();
      }
    });
  }
}

提示:并不需要在stop方法中手動卸載某個verticle的子verticles(child verticles)肮蛹,因為Vert.x會在父verticle被卸載時自動卸載它們。

Verticle的類型

有三種不同類型的verticle创南。

標(biāo)準(zhǔn)verticle(Standard Verticles)

這是最平常并有用的版本蔗崎,它們會一直由同一個event loop線程執(zhí)行。下一節(jié)里我們會更詳細地討論這個扰藕。

Worker Verticles

這一類由worker pool里的線程運行。絕不會有超過一個線程并發(fā)地執(zhí)行單一實例芳撒。

多線程版(Multi-threaded) worker verticles

這些還是由worker pool里的線程運行邓深,不過單一實例可以被多個線程并發(fā)執(zhí)行。

標(biāo)準(zhǔn)verticle

標(biāo)準(zhǔn)verticle被創(chuàng)建的時候笔刹,它們會被指定給一個event loop線程芥备,然后event loop會調(diào)用其start方法。當(dāng)你從event loop調(diào)用任意核心API上可以接受handler的方法時舌菜,Vert.x保證那些handlers會由同樣的event loop執(zhí)行萌壳。

這意味著我們可以保證一個verticle實例里的所有代碼都會在同一個event loop上執(zhí)行(只要你不自己創(chuàng)建線程并調(diào)用它!)日月。

這同樣意味著可以像單線程應(yīng)用那樣來寫所有代碼袱瓮,至于多線程并發(fā)和擴展的問題交給Vert.x就可以了。不需要有同步和易變性(volatile)的困擾爱咬,這樣你也可以避免‘傳統(tǒng)的’手寫多線程應(yīng)用中普遍會碰到的狀況尺借,譬如競爭條件(race conditions)和死鎖(deadlock)。

Worker Verticles

worker verticle和標(biāo)準(zhǔn)verticle挺像的精拟,不同點在于worker verticle由Vert.x的worker 線程池中的線程執(zhí)行燎斩,而標(biāo)準(zhǔn)verticle由event loop執(zhí)行虱歪。

worker verticle是為調(diào)用阻塞代碼而設(shè)計的。它們不會阻塞任意的event loop栅表。

如果你不想用worker verticle來執(zhí)行阻塞代碼笋鄙,那也可以通過直接運行內(nèi)聯(lián)阻塞代碼的方式(就是前文所述的executeBlocking)。

如果你想將某個verticle作為worker verticle部署怪瓶,可以通過調(diào)用setWorker方法萧落。

DeploymentOptions options = new DeploymentOptions().setWorker(true);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

worker verticle實例絕不會被多個線程并發(fā)執(zhí)行,但可以被不同線程在不同時候執(zhí)行劳殖。

多線程版worker verticle

一個多線程版worker verticle就像普通的worker verticle一般铐尚,但它可以被不同線程并發(fā)執(zhí)行。

警告:多線程版worker 線程是個高級特性哆姻,絕大多數(shù)應(yīng)用對此并無需求宣增。為了這些verticle的并發(fā)執(zhí)行,你需要使用標(biāo)準(zhǔn)的Java多線程編程技能矛缨,小心地使verticles保持一致的狀態(tài)爹脾。

以編程的方式部署verticle

你可以使用deployVerticle系列方法中的一個來部署verticle,只需要知道verticle的名稱或者你自己創(chuàng)建一個verticle實例丟過去箕昭。

注意:部署verticle實例的方式是Java專有的灵妨。

Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);

你也可以通過指定verticle的名稱來部署。

verticle的實例化需要用到特定的VerticleFactory落竹,verticle的名稱就是用來查詢這個特定的工廠類泌霍。

不同的語言有不同的工廠類,用來初始化verticle述召。原因多種多樣朱转,比如運行時從Maven加載服務(wù)或者獲取verticles。

這樣你可以部署任意以Vert.x支持的語言寫就的verticle积暖。

下面是一個部署不同種verticle的例子:

vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle");

// Deploy a JavaScript verticle
vertx.deployVerticle("verticles/myverticle.js");

// Deploy a Ruby verticle verticle
vertx.deployVerticle("verticles/my_verticle.rb");

從verticle的名稱映射到verticle factory的規(guī)則

當(dāng)使用名稱部署verticle時藤为,名稱的作用是選出實際中用來實例化這個verticle的verticle factory。

verticle的名稱可以有一個前綴:前綴是個字符串夺刑,后面緊跟著一個冒號缅疟;如果前綴存在將被用于查詢對應(yīng)的factory。
即:
js:foo.js // Use the JavaScript verticle factory
groovy:com.mycompany.SomeGroovyCompiledVerticle // Use the Groovy verticle factory
service:com.mycompany:myorderservice // Uses the service verticle factory

如果沒有前綴遍愿,Vert.x會尋找后綴來查詢factory存淫。
即:
foo.js // Will also use the JavaScript verticle factory
SomeScript.groovy // Will use the Groovy verticle factory

如果前后綴都不存在,那么Vert.x會假定這是一個完全限定類名(FQCN)的Java verticle错览,并試著循此實例化纫雁。

Verticle Factories如何定位呢?

絕大多數(shù)verticle factories都是從類路徑(classpath)中加載的倾哺,在Vert.x啟動時注冊轧邪。

同樣地刽脖,如果你希望用編程的方式注冊、注銷verticle factories忌愚,那么有registerVerticleFactoryunregisterVerticleFactory可用曲管。

等待部署完成

verticle的部署是異步進行的,可能完成的時候?qū)Σ渴鸱椒ǖ恼{(diào)用都已經(jīng)返回一陣子了硕糊。

如果你想在部署完成時收到通知院水,可以在部署時指定一個完成處理器(completion handler):

vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", res -> {
  if (res.succeeded()) {
    System.out.println("Deployment id is: " + res.result());
  } else {
    System.out.println("Deployment failed!");
  }
});

如果部署成功,此handler會收到一個字符串简十,這里面包含了部署的ID檬某。

后面在你卸載這次部署的verticle時,會用到這個ID螟蝙。

卸載部署的verticle

可以使用undeploy來卸載已部署的verticle恢恼。

卸載本身也是異步的。所以如果你想在完成的時候收到通知胰默,處理方法同部署的時候:

vertx.undeploy(deploymentID, res -> {
  if (res.succeeded()) {
    System.out.println("Undeployed ok");
  } else {
    System.out.println("Undeploy failed!");
  }
});

指定verticle實例的數(shù)量

用verticle的名稱部署時场斑,可以指定verticle實例的數(shù)量:

DeploymentOptions options = new DeploymentOptions().setInstances(16);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

這個特性在擴展到多核cpu上時很有幫助。比如你要部署一個web服務(wù)器的verticle牵署,并且你的機器上有多個核心漏隐;為了這多個核心能充分發(fā)揮自己的光和熱括蝠,你可以部署上多個實例靠柑。

給verticle傳遞配置參數(shù)

部署時可以將配置以JSON的形式傳遞給verticle:

JsonObject config = new JsonObject().put("name", "tim").put("directory", "/blah");
DeploymentOptions options = new DeploymentOptions().setConfig(config);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

之后配置信息將可通過Context對象使用戚丸,或者直接使用config方法蜈首。

返回的配置是一個JSON對象,所以你可以像下面這樣取數(shù)據(jù):
System.out.println("Configuration: " + config().getString("name"));

在verticle中訪問環(huán)境變量

環(huán)境變量和系統(tǒng)屬性可以用Java API訪問:

System.getProperty("prop");
System.getenv("HOME");

verticle隔離組(Verticle Isolation groups)

缺省情況下阀坏,Vert.x有一個扁平的類路徑(flat classpath)蹋订,部署vertilce時橄教,Vert.x會使用當(dāng)前的類加載器(classloader)--而不是創(chuàng)建一個新的者填。多數(shù)情況下,這都是最簡單做葵、清晰占哟、穩(wěn)健的做法。

然而酿矢,有時候你可能想把某些verticle的部署與其他的隔離開來榨乎。

譬如,你想在同一個Vert.x實例中部署同一個verticle的不同版本瘫筐,他倆還有著相同的類名蜜暑;又或者你的兩個不同verticle分別用到了同一個類庫的不同版本。

使用隔離組時策肝,你需要提供待隔離類的名稱列表肛捍。方法setIsolatedClasses可以搞定這個事隐绵。傳入的名稱可以是類似com.mycompany.myproject.engine.MyClass這樣的完全限定類名;還可以是類似com.mycompany.myproject.這樣帶通配符的拙毫,這會匹配到包com.mycompany.myproject*里的任意類和任意子包依许。

請注意唯有匹配到的類才會被隔離,其他的類仍然由當(dāng)前的類加載器載入缀蹄。

如果想從非主類路徑中加載類和資源峭跳,那你可以用setExtraClasspath方法提供額外的類路徑條目。

警告:使用這個特性要小心缺前。類加載器們也可能帶來一堆bug蛀醉,使你的排錯工作變得困難(譯注:大家知道bug在英文里有蟲子和計算機程序錯誤的意思;所以此處前面說蟲子衅码,后面說除錯困難)拯刁。

這兒有個利用隔離組來隔離一個verticle的部署的例子。

DeploymentOptions options = new DeploymentOptions().setIsolationGroup("mygroup");
options.setIsolatedClasses(Arrays.asList("com.mycompany.myverticle.*",
                   "com.mycompany.somepkg.SomeClass", "org.somelibrary.*"));
vertx.deployVerticle("com.mycompany.myverticle.VerticleClass", options);

高可用性

部署verticle時可以打開高可用性(HA)肆良,在這樣的上下文環(huán)境里筛璧,若某個Vert.x實例上的某個vertilce意外地掛掉,集群里的另一個Vert.x實例將會重新部署這個verticle惹恃。

以高可用性運行verticle時夭谤,只需要在命令行后面加上** -ha **:
vertx run my-verticle.js -ha

打開高可用性時,無需添加** -cluster **巫糙。

更多關(guān)于高可用性和配置的細節(jié)可以在下面的高可用性和故障轉(zhuǎn)移(High Availability and Fail-Over)一節(jié)中找到朗儒。

從命令行運行verticles

將依賴添加到Vert.x核心包里,就能以正常方式在你的maven或gradle項目中直接使用Vert.x参淹。

不過你也可以直接從命令行運行verticle醉锄。

為了達成這個目的,你要下載并安裝好Vert.x的發(fā)布包浙值,并將安裝目錄下的** bin 目錄添加到 PATH 環(huán)境變量恳不,同樣要確保Java8 的JDK在 PATH **里。

注意:為了動態(tài)編譯Java代碼开呐,JDK是必要的(言下之意烟勋,只裝JRE是不夠的)。

一切就緒筐付,現(xiàn)在可以用** vertx run **運行verticle了卵惦。下面有幾個例子:

# Run a JavaScript verticle
vertx run my_verticle.js

# Run a Ruby verticle
vertx run a_n_other_verticle.rb

# Run a Groovy script verticle, clustered
vertx run FooVerticle.groovy -cluster

至于Java verticle,甚至不需要編譯你就可以直接運行它瓦戚!
vertx run SomeJavaSourceFile.java

Vert.x會在運行前動態(tài)地編譯它沮尿。這點在快速建立原型和演示時特別有用。不需要先設(shè)置Maven或Gradle就能開始了较解。

有關(guān)在命令行執(zhí)行vertx時各種其他可用的選項的所有信息畜疾,只需要在命令行輸入vertx即可獲得赴邻。

退出Vert.x

Vert.x實例維護的進程不是守護進程(daemon threads),所以它們會阻止JVM退出庸疾。

如果你以嵌入的方式使用Vert.x乍楚,工作完成的時候,你可以調(diào)用close方法關(guān)閉它届慈。

這樣做會關(guān)閉所有內(nèi)部線程池徒溪、其他的資源,并允許JVM退出金顿。

上下文對象(The Context object)

Vert.x給handler提供事件臊泌、或者調(diào)用verticle的start/stop方法時,其執(zhí)行狀況都是與一個Context(上下文)聯(lián)系在一起的揍拆。通常渠概,這個context是一個與特定的event loop線程綁定的event-loop context。所以與此context相關(guān)的執(zhí)行動作都發(fā)生在同一確定的event loop線程上嫂拴。至于worker verticle和運行內(nèi)聯(lián)的阻塞代碼時播揪,會有一個worker context與之關(guān)聯(lián),這些動作都由worker 線程池里的線程運行筒狠。

利用getOrCreateContext方法猪狈,可以獲得上下文環(huán)境:
Context context = vertx.getOrCreateContext();

如果當(dāng)前線程已經(jīng)存在一個context與之關(guān)聯(lián),它會重用這個context對象辩恼。否則會創(chuàng)建context的一個新實例雇庙。可以像下面這樣測試下取到的context的類型

Context context = vertx.getOrCreateContext();
if (context.isEventLoopContext()) {
  System.out.println("Context attached to Event Loop");
} else if (context.isWorkerContext()) {
  System.out.println("Context attached to Worker Thread");
} else if (context.isMultiThreadedWorkerContext()) {
  System.out.println("Context attached to Worker Thread - multi threaded worker");
} else if (! Context.isOnVertxThread()) {
  System.out.println("Context not attached to a thread managed by vert.x");
}

在你拿到一個context對象后灶伊,可以在此context里異步地運行代碼疆前。換句話說,你提交的任務(wù)最終會運行在同樣的context里:

vertx.getOrCreateContext().runOnContext( (v) -> {
  System.out.println("This will be executed asynchronously in the same context");
});

當(dāng)有數(shù)個handler運行在同一context里時聘萨,它們可能會希望共享一些數(shù)據(jù)竹椒。context對象提供了存取共享在上下文里數(shù)據(jù)的方法。例如米辐,你要傳遞數(shù)據(jù)過去做點事碾牌,可以用runOnContext方法:

final Context context = vertx.getOrCreateContext();
context.put("data", "hello");
context.runOnContext((v) -> {
  String hello = context.get("data");
});

context對象也允許你通過config方法訪問verticle的配置信息。去看看** 給verticle傳遞配置 **一節(jié)吧儡循,你將獲得關(guān)于此項配置的更多細節(jié)。

定期征冷、延時執(zhí)行

在Vert.x里择膝,延時或定期執(zhí)行是很普遍的。

在標(biāo)準(zhǔn)verticle里检激,你不能以使線程休眠的方式引入延遲肴捉;這樣干會阻塞event loop線程腹侣。

取而代之的是Vert.x定時器,定時器分為一次性(one-shot)周期性(periodic)的齿穗。下面我們會分別討論傲隶。

一次性定時器

一段確定的延時過后,一次性定時器將調(diào)用事件handler窃页,度量衡是毫秒跺株。

設(shè)置一個觸發(fā)一次的定時器用到setTimer方法,它有兩個參數(shù):延時和一個handler脖卖。

long timerID = vertx.setTimer(1000, id -> {
  System.out.println("And one second later this is printed");
});

System.out.println("First this is printed");

返回值是定時器的ID(long類型)乒省,它具有唯一屬性。之后你可以用這個ID來取消定時器畦木。這個handler也會收到定時器的ID袖扛。

周期性定時器

類似的,利用setPeriodic方法可以設(shè)置一個定期觸發(fā)的定時器十籍。

初始的延遲值就是周期間隔蛆封。

返回值與一次性定時器一樣,此處不再贅述勾栗。

定時器的事件handler的參數(shù)也與一次性定時器一致:

記住惨篱,定時器會定期觸發(fā)。如果你的定期處理需要耗費大量時間械姻,你的定時器事件可能會連續(xù)運行甚至糟糕到堆積在一起妒蛇。

在這種情況下,你應(yīng)該考慮轉(zhuǎn)而使用setTimer楷拳。一旦你的處理完成了绣夺,你可以再設(shè)置下一個定時器。

long timerID = vertx.setPeriodic(1000, id -> {
  System.out.println("And every second this is printed");
});

System.out.println("First this is printed");

取消定時器

像下面這樣欢揖,調(diào)用cancelTimer方法陶耍,指定定時器ID,即可取消周期定時器她混。

verticle里的自動清理(Automatic clean-up in verticles)

如果你是在verticle內(nèi)部創(chuàng)建的定時器烈钞,那么verticle被卸載時,這些定時器將被自動關(guān)閉坤按。


事件總線(The Event Bus)

event bus是Vert.x的神經(jīng)系統(tǒng)毯欣。

每個Vert.x實例都擁有單獨的一個event bus實例,你可以通過eventBus方法得到它臭脓。

應(yīng)用的不同部分酗钞,不管是否在同一個Vert.x實例里,即使是不同語言編寫的,都可以通過event bus彼此交流砚作。

甚至瀏覽器里運行的的客戶端JavaScript也可以通過同一個event bus相互通信窘奏。

event bus在多個服務(wù)器和多個瀏覽器間形成了一個分布式的點對點消息系統(tǒng)。

event bus支持發(fā)布/訂閱(publish/subscribe)葫录、點對點着裹、請求-響應(yīng)(request-response)這三種消息模式。

event bus的API很簡單米同,主要包括注冊handlers骇扇,注銷handlers,發(fā)送和發(fā)布消息窍霞。

基本概念

尋址(Addressing)

消息通過event bus發(fā)送到某個地址(address)匠题。

Vert.x沒有花哨的令人困擾的尋址方案。Vert.x里地址就是字符串但金。任意字符串都有效韭山。不過使用某種命名策略還是很明智的,例如使用分隔符限定命名空間冷溃。

這里是一些有效的地址:europe.news.feed1, acme.games.pacman, sausages, and X钱磅。

處理器(Handlers)

消息由handlers接收,所以你需要把handler注冊到地址上似枕。

同一個地址可以注冊多個不同的handler盖淡。

某個handler也可以被注冊在多個不同的地址上。

發(fā)布/訂閱消息(Publish / subscribe messaging)

event bus支持發(fā)布(publishing)消息凿歼。

消息被發(fā)布到某個地址褪迟,這意味著把消息分發(fā)到注冊在此地址上的所有handlers。

這就是我們很熟悉的發(fā)布/訂閱模式答憔。

點對點和請求-響應(yīng)(Point to point and Request-Response messaging)

event bus也支持點對點消息味赃。

當(dāng)消息被發(fā)送到某個地址,Vert.x會把消息路由給注冊在此地址上的某個handler蓉驹。

如此此地址上注冊了超過一個handler态兴,Vert.x將會通過一個不嚴(yán)格的輪詢算法(non-strict round-robin algorithm)從中選擇一個狠持。

在點對點的消息機制中献汗,發(fā)消息時可以選擇指定一個應(yīng)答handler(reply handler)楚午。

當(dāng)有接收者收到消息并處理后阱驾,接收者可以選擇是否答復(fù)此消息。如果選擇答復(fù),上述的reply handler將被調(diào)用隧甚。

發(fā)送者收到消息回應(yīng)后戚扳,同樣可以做出回應(yīng)超歌。這可以無限地重復(fù)下去辐董,并允許兩個不同的verticle間形成對話。

這種通用的消息模式稱為請求-響應(yīng)模式。

盡力分發(fā)(Best-effort delivery)

Vert.x會盡最大的努力分發(fā)消息退子,絕不會有意丟棄某些消息七兜。這被稱為盡力分發(fā)撞叨。

然而虽界,在event bus部分或全部失效的情況下碗暗,消息還是有可能丟失。

如果你的應(yīng)用很關(guān)心這一點死姚,那么編碼時應(yīng)該注意使你的handler是冪等的(be idempotent)碰缔,并且發(fā)送方應(yīng)該在恢復(fù)后嘗試重新發(fā)送消息。

消息的類型

任何的基本類型/簡單類型,字符串或者buffers都可以被當(dāng)成消息發(fā)送出去禀晓。

但是Vert.x里通常使用JSON格式的消息。

在Vert.x支持的語言里,創(chuàng)建恨豁、讀取、解析JSON都很容易跌捆,所以它就成了Vert.x上的通用語(lingua franca)说订。

當(dāng)然了,并不是必須使用JSON。

event bus是很靈活的,支持在其上發(fā)送任意專有的對象媚媒。你只需為此定義一個編解碼器(codec)

The Event Bus API

下面讓我們來看看API。

獲取event bus對象

可以像下面這樣拿到event bus對象的引用:
EventBus eb = vertx.eventBus();

每個Vert.x實例有唯一的event bus實例。

注冊handlers

注冊handler最簡單的方法是用consumer方法。看例子:

EventBus eb = vertx.eventBus();

eb.consumer("news.uk.sport", message -> {
  System.out.println("I have received a message: " + message.body());
});

當(dāng)你的handler收到一條消息時,handler會被調(diào)用,而消息(message)會作為參數(shù)傳遞過去。

consumer方法會返回一個MessageConsumer實例。

這個對象可以用來注銷handler,或?qū)andler當(dāng)作流(stream)來使用。

或者你也可以用consumer方法得到一個未設(shè)置handler的MessageConsumer 對象,隨后再設(shè)置handler:

EventBus eb = vertx.eventBus();

MessageConsumer<String> consumer = eb.consumer("news.uk.sport");
consumer.handler(message -> {
  System.out.println("I have received a message: " + message.body());
});

當(dāng)在一個集群event bus上注冊了handler時继控,完成在集群上每個節(jié)點的注冊需要花點時間珊搀。

如果你想在完成時得到通知派诬,你可以在MessageConsumer 對象上注冊一個completion handler

consumer.completionHandler(res -> {
  if (res.succeeded()) {
    System.out.println("The handler registration has reached all nodes");
  } else {
    System.out.println("Registration failed!");
  }
});

注銷handlers

注銷handler可以通過調(diào)用unregister方法完成耀里。

在集群event bus做這件事時咙鞍,同樣要花點時間等其傳播到各個節(jié)點,所以你也可以通過unregister方法得到通知。

consumer.unregister(res -> {
  if (res.succeeded()) {
    System.out.println("The handler un-registration has reached all nodes");
  } else {
    System.out.println("Un-registration failed!");
  }
});

發(fā)布消息

發(fā)布一個消息只需簡單地調(diào)用publish方法,指定要發(fā)往的地址。
eventBus.publish("news.uk.sport", "Yay! Someone kicked a ball");

這個消息會被分發(fā)到注冊在地址 news.uk.sport 上的所有handlers怕享。

發(fā)送消息

發(fā)送消息的結(jié)果是注冊在此地址上的handler只有一個會收到消息余境。這是點對點的消息模式。

你可以用send方法發(fā)送消息:
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball");

設(shè)置消息頭(Setting headers on messages)

event bus 上傳輸?shù)南⒁部梢园恍┫㈩^(headers)顽聂。

可以在發(fā)送/發(fā)布消息時指定一個DeliveryOptions對象來做這件事:

DeliveryOptions options = new DeliveryOptions();
options.addHeader("some-header", "some-value");
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options);

消息的順序

Vert.x會將消息以發(fā)送者送出的順序分發(fā)給handler。

消息對象

你在handler里收到的消息是一個Message對象滞伟。

消息的body就是發(fā)送過來的對象。

消息頭可以通過headers方法得到。

消息/發(fā)送回應(yīng)的知識點(Acknowledging messages / sending replies)

使用send方法時event bus會嘗試把消息發(fā)送到注冊在event bus上的MessageConsumer對象。

某些情況下,發(fā)送方可能想知道消息已經(jīng)被接收并處理了忆肾。

為了讓發(fā)放方了解消息已被處理稳强,consumer可以通過調(diào)用reply方法給予回應(yīng)褒繁。

如果這么做了,那么發(fā)送方將會收到一個回應(yīng),并且回應(yīng)handler將被調(diào)用吵血≌炝恚看下面這個例子:
接收方:

MessageConsumer<String> consumer = eventBus.consumer("news.uk.sport");
consumer.handler(message -> {
  System.out.println("I have received a message: " + message.body());
  message.reply("how interesting!");
});

發(fā)送方:

eventBus.send("news.uk.sport", "Yay! Someone kicked a ball across a patch of grass", ar -> {
  if (ar.succeeded()) {
    System.out.println("Received reply: " + ar.result().body());
  }
});

回應(yīng)可以帶一個消息體殿托,你可以在其中放置一些有用的信息鸠按。

“處理”實際上是由應(yīng)用程序定義的扎运,其中會發(fā)生什么完全依賴于consumer做了什么鬼吵;Vert.x的event bus并不知道也不關(guān)心這些。

例如:

  • 一個簡單的消息consumer遣蚀,它實現(xiàn)了返回當(dāng)天時間的服務(wù);可以確認(rèn)回應(yīng)的消息體中包含了這個時間。
  • 一個實現(xiàn)了持久化隊列的消息consumer澎媒,如果消息被成功地持久化在存儲中可以確認(rèn)是true,反之則為false
  • 一個處理訂單的消息consumer勾笆,當(dāng)訂單被成功處理時敌蚜,它可以從數(shù)據(jù)庫里被刪掉,這時候可以確認(rèn)是true窝爪。

指定超時時間的發(fā)送(Sending with timeouts)

在發(fā)送一個帶回應(yīng)handler的消息時,可以在DeliveryOptions對象里指定超時的時間蒲每。

如果在這段時間里沒有收到回應(yīng)纷跛,回應(yīng)handler將被調(diào)用,并以失敗結(jié)束邀杏。

缺省的超時時間是30秒贫奠。

發(fā)送失敗(Send Failures)

消息發(fā)放也可能因為其他原因失敗,包括:

  • 消息發(fā)送到的地址沒有handler可用望蜡。
  • 接收方顯示地調(diào)用fail方法返回了失敗的信息唤崭。

所有的情況下回應(yīng)的handler都會被調(diào)用,并返回特定的錯誤脖律。

消息編解碼器(Message Codecs)

只要你定義并注冊了相關(guān)的message codec谢肾,就可以在event bus上發(fā)送任意的對象。

當(dāng)發(fā)送/發(fā)布消息時小泉,你需要在DeliveryOptions對象上指定codec的名稱:

eventBus.registerCodec(myCodec);

DeliveryOptions options = new DeliveryOptions().setCodecName(myCodec.name());

eventBus.send("orders", new MyPOJO(), options);

如果你想一直使用同一個codec芦疏,可以將它注冊成缺省的codec,這樣后面就不用每次發(fā)消息時再專門指定:

eventBus.registerDefaultCodec(MyPOJO.class, myCodec);

eventBus.send("orders", new MyPOJO());

unregisterCodec方法可以用來注銷codec微姊。

消息codec并不總是對同樣的類型進行編碼酸茴、解碼。例如柒桑,你可以寫一個codec用來發(fā)送MyPOJO類弊决,而當(dāng)消息送達handler時噪舀,可以是MyOtherPOJO類魁淳。

集群event bus

event bus并不只是存在于單一的Vert.x實例中。將多個Vert.x實例組成集群后与倡,可以形成一個單獨的界逛,分布式的event bus。

以編程實現(xiàn)集群

如果你以編碼的方式創(chuàng)建了Vert.x實例纺座,將Vert.x實例配置成集群式的即可得到集群event bus息拜;

VertxOptions options = new VertxOptions();
Vertx.clusteredVertx(options, res -> {
  if (res.succeeded()) {
    Vertx vertx = res.result();
    EventBus eventBus = vertx.eventBus();
    System.out.println("We now have a clustered event bus: " + eventBus);
  } else {
    System.out.println("Failed: " + res.cause());
  }
});

當(dāng)然你應(yīng)該確保classpath中有一個ClusterManager的實現(xiàn),例如缺省的HazelcastClusterManager

命令行里實現(xiàn)集群

你可以在命令行里運行集群vertx:
vertx run my-verticle.js -cluster

verticle的自動清理

如果你在verticle內(nèi)部注冊了event bus handlers少欺,當(dāng)這些verticle被卸載時handlers將被自動注銷喳瓣。


JSON

與其他語言不同,Java對JSON并沒有提供頭等的支持赞别;所以我們提供了兩個類畏陕,使得JSON的處理容易些。

JSON 對象

JsonObject類用來表示JSON對象仿滔。

JSON對象基本可以認(rèn)為是個map惠毁,鍵是字符串,而值可以是JSON支持的類型中的一種(字符串崎页、數(shù)字鞠绰、布爾型)。

JSON對象也支持null 值飒焦。

創(chuàng)建JSON對象

默認(rèn)的構(gòu)造器可以創(chuàng)建一個空的JSON對象蜈膨。

你也可以從JSON格式的字符串創(chuàng)建一個JSON對象:

String jsonString = "{\"foo\":\"bar\"}";
JsonObject object = new JsonObject(jsonString);

向JSON對象中添加條目

put方法可以用于往JSON對象中添加條目。

put方法支持流式API:

JsonObject object = new JsonObject();
object.put("foo", "bar").put("num", 123).put("mybool", true);

從JSON對象中取值

可以使用類似getXXX方法從JSON對象中取值荒给,例如:

String val = jsonObject.getString("some-key");
int intVal = jsonObject.getInteger("some-other-key");

將JSON對象編碼為字符串

encode方法用來將JSON對象轉(zhuǎn)換為字符串丈挟。

JSON 數(shù)組

JsonArray類用來表示JSON 數(shù)組。

一個JSON 數(shù)組是一些值(字符串志电、數(shù)字或者布爾型)組成的序列曙咽。

JSON 數(shù)組也可以包括null 。

創(chuàng)建JSON 數(shù)組

默認(rèn)的構(gòu)造器可以創(chuàng)建一個空的JSON 數(shù)組挑辆。

可以從JSON格式的字符串創(chuàng)建JSON 數(shù)組:

String jsonString = "[\"foo\",\"bar\"]";
JsonArray array = new JsonArray(jsonString);

往JSON數(shù)組中添加元素

add方法:

JsonArray array = new JsonArray();
array.add("foo").add(123).add(false);

從JSON 數(shù)組中取值

類似下面這樣:

String val = array.getString(0);
Integer intVal = array.getInteger(1);
Boolean boolVal = array.getBoolean(2);

將JSON 數(shù)組轉(zhuǎn)換為字符串

使用encode即可例朱。


Buffers

Vert.x中大量使用buffers傳輸數(shù)據(jù)。

buffer是一個可以讀寫的字節(jié)序列鱼蝉,超出其容量時洒嗤,它會自動擴展】啵可以將其看成一個智能的字節(jié)數(shù)組渔隶。

創(chuàng)建buffers

可以使用靜態(tài)方法Buffer.buffer創(chuàng)建buffers。

buffer可以由字符串或字節(jié)數(shù)組初始化洁奈,當(dāng)然间唉,空的buffer也是允許的。

這里有些例子利术〕室埃空buffer:
Buffer buff = Buffer.buffer();

字符串初始化的buffer,這里的字符串將使用UTF-8編碼成buffer印叁。
Buffer buff = Buffer.buffer("some string");

或者你可以指定編碼:
Buffer buff = Buffer.buffer("some string", "UTF-16");

從字節(jié)數(shù)組創(chuàng)建:

byte[] bytes = new byte[] {1, 3, 5};
Buffer buff = Buffer.buffer(bytes);

如果你知道將會有多少數(shù)據(jù)待寫入被冒,可以在創(chuàng)建buffer時指定buffer的尺寸军掂。這樣buffer創(chuàng)建時就會分配這么多內(nèi)存,這在效率上要優(yōu)過邊寫入邊擴容昨悼。

注意蝗锥,這樣創(chuàng)建的buffer仍然是空的(empty)。創(chuàng)建時并不會有0填充于其中率触。
Buffer buff = Buffer.buffer(10000);

寫buffer

寫入buffer有兩種方式:附加(appending)玛追、隨機存取(random access)。這兩種方式下闲延,buffer都會自動擴容痊剖。不會產(chǎn)生**IndexOutOfBoundsException **異常。

Appending to a Buffer

往buffer上附加信息垒玲,可以使用**appendXXX **系列方法陆馁。有適合各種類型的append方法。

append系列方法的返回值就是buffer本身合愈,所以適用鏈?zhǔn)綄懛ǎ?/p>

Buffer buff = Buffer.buffer();

buff.appendInt(123).appendString("hello\n");

socket.write(buff);

Random access buffer writes

你也可以通過一系列**setXXX **方法在指定的索引處寫入數(shù)據(jù)叮贩。set系列方法的第一個參數(shù)都是索引值。

buffer會自動擴容的佛析。

Buffer buff = Buffer.buffer();

buff.setInt(1000, 123);
buff.setString(0, "hello");

讀buffer

**getXXX **系列方法用來從buffer中讀取數(shù)據(jù)益老。get系列方法的第一個參數(shù)也是指示從哪開始讀的索引值。

Buffer buff = Buffer.buffer();
for (int i = 0; i < buff.length(); i += 4) {
  System.out.println("int value at " + i + " is " + buff.getInt(i));
}

使用無符號數(shù)

可以使用**getUnsignedXXX寸莫、appendUnsignedXXX捺萌、setUnsignedXXX **系列方法讀寫buffer。當(dāng)你在為網(wǎng)絡(luò)協(xié)議實現(xiàn)編解碼器時膘茎,如果想將帶寬消耗優(yōu)化到極致桃纯,這個特性能幫上忙。

下面這個例子里披坏,使用一個字節(jié)在指定位置寫入200:

Buffer buff = Buffer.buffer(128);
int pos = 15;
buff.setUnsignedByte(pos, (short) 200);
System.out.println(buff.getUnsignedByte(pos));

控制臺將顯示‘200’态坦。

buffer的長度

length方法可以獲得buffer的長度。buffer的長度是最大的索引值+1棒拂。

復(fù)制buffer

使用copy方法伞梯。

將buffer分片(slicing buffers)

slice方法用來將buffer分片,切分出來的新buffer與原buffer共享緩存區(qū)帚屉。

buffer重用

在buffer被寫入socket或類似地方后谜诫,它就不能再被使用了。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涮阔,一起剝皮案震驚了整個濱河市猜绣,隨后出現(xiàn)的幾起案子灰殴,更是在濱河造成了極大的恐慌敬特,老刑警劉巖掰邢,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伟阔,居然都是意外死亡辣之,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門皱炉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀估,“玉大人,你說我怎么就攤上這事合搅《嗖螅” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵灾部,是天一觀的道長康铭。 經(jīng)常有香客問我,道長赌髓,這世上最難降的妖魔是什么从藤? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮锁蠕,結(jié)果婚禮上夷野,老公的妹妹穿的比我還像新娘。我一直安慰自己荣倾,他們只是感情好悯搔,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舌仍,像睡著了一般鳖孤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天爬早,我揣著相機與錄音吆寨,去河邊找鬼。 笑死平匈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藏古。 我是一名探鬼主播增炭,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拧晕!你這毒婦竟也來了隙姿?” 一聲冷哼從身側(cè)響起输玷,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤侠畔,失蹤者是張志新(化名)和其女友劉穎斥黑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伐庭,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡扰路,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年识椰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幽七。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辜羊,死狀恐怖骤肛,靈堂內(nèi)的尸體忽然破棺而出纳本,到底是詐尸還是另有隱情,我是刑警寧澤腋颠,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布繁成,位于F島的核電站,受9級特大地震影響淑玫,放射性物質(zhì)發(fā)生泄漏巾腕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一絮蒿、第九天 我趴在偏房一處隱蔽的房頂上張望尊搬。 院中可真熱鬧,春花似錦土涝、人聲如沸佛寿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冀泻。三九已至,卻和暖如春蜡饵,著一層夾襖步出監(jiān)牢的瞬間腔长,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工验残, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捞附,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓您没,卻偏偏與公主長得像鸟召,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子氨鹏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容