本文依照 知識共享許可協(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 厦幅。
看文檔的說明,all
和 join
的區(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忌愚,那么有registerVerticleFactory和unregisterVerticleFactory可用曲管。
等待部署完成
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或類似地方后谜诫,它就不能再被使用了。