Vert.x是一個事件驅(qū)動的JVM上的框架乎串,可以幫助我們構(gòu)建現(xiàn)代、靈活速警、可擴(kuò)展的程序叹誉。Vert.x有多種語言的版本,可以用在Java闷旧、Kotlin长豁、Scala、Groovy忙灼、Ruby等語言上匠襟。當(dāng)然現(xiàn)在討論的是如何在Java上使用Vert.x。
Vert.x是一個比較大的框架该园,包含了各個方面的功能酸舍。所以我決定寫幾篇文章,分別來介紹這些功能里初。所以今天先來看看Vert.x最核心的一些功能吧啃勉,這些功能都在vertx-core包下。官方的英文文檔在這里青瀑,本文參考和引用了Vertx官方文檔上的一些內(nèi)容璧亮,如果需要詳細(xì)信息請直接看官方文檔萧诫。當(dāng)然我又發(fā)現(xiàn)了志愿者翻譯的中文文檔,質(zhì)量也可以枝嘶,只不過版本稍微落后一些帘饶。
Vert.x核心庫包含了以下一些功能,它們都是比較底層的功能群扶,開發(fā)者可以根據(jù)需要使用及刻。當(dāng)然由于Vert.x的功能很多,所以這里我不打算全部介紹竞阐,只準(zhǔn)備介紹一些比較常用的功能缴饭。如果想了解全部功能的話,還是請參考官方文檔骆莹。
- TCP客戶端和服務(wù)端
- HTTP客戶端和服務(wù)端以及WebSockets支持
- 事件總線
- 共享數(shù)據(jù)颗搂,包括本地maps以及分布式聚簇maps
- 周期性和延遲操作
- 數(shù)據(jù)報
- DNS客戶端
- 文件系統(tǒng)訪問
- 高可用性
- 聚簇
Vert.x的特點(diǎn)是事件驅(qū)動、流式編程和非阻塞幕垦,這些特點(diǎn)將會在后面逐一介紹丢氢。
引入依賴
如果使用Maven的話,在pom.xml
中添加以下一段即可先改。
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>3.4.2</version>
</dependency>
如果使用Gradle的話疚察,在build.gradle
中添加以下一段。
dependencies {
compile 'io.vertx:vertx-core:3.4.2'
}
開始使用
創(chuàng)建Vertx對象
要使用Vertx的第一步就是創(chuàng)建Vertx對象仇奶,所有API都要通過這個對象來調(diào)用貌嫡。一般情況下,一個程序只需要一個Vertx對象即可该溯,不過有時候為了程序隔離等原因岛抄,我們會需要多個Vertx對象。創(chuàng)建Vertx對象很簡單朗伶,調(diào)用下面一行代碼即可弦撩。
Vertx vertx = Vertx.vertx();
有時候可能需要對Vertx進(jìn)行一些配置步咪,可以通過添加程序參數(shù)來實現(xiàn)论皆。
VertxOptions options = new VertxOptions();
options.setWorkerPoolSize(20);
Vertx vertx2 = Vertx.vertx(options);
Verticles
Verticles是Vertx中的一個模型,可以幫助我們封裝代碼猾漫。Verticles是一個可選的模型点晴,所以即使我們不使用Verticles,也可以繼續(xù)使用Vertx悯周。Verticles說起來很簡單粒督,就是一個接口。當(dāng)然實際情況下禽翼,一般都是繼承AbstractVerticle
抽象類屠橄。
public abstract class AbstractVerticle implements Verticle {
protected Vertx vertx;
protected Context context;
@Override
public Vertx getVertx() {
return vertx;
}
@Override
public void init(Vertx vertx, Context context) {
this.vertx = vertx;
this.context = context;
}
public String deploymentID() {
return context.deploymentID();
}
public JsonObject config() {
return context.config();
}
public List<String> processArgs() {
return context.processArgs();
}
@Override
public void start(Future<Void> startFuture) throws Exception {
start();
startFuture.complete();
}
@Override
public void stop(Future<Void> stopFuture) throws Exception {
stop();
stopFuture.complete();
}
public void start() throws Exception {
}
public void stop() throws Exception {
}
}
繼承AbstractVerticle
抽象類之后族跛,必須實現(xiàn)的方法是start()
,它會在Verticle部署的時候調(diào)用锐墙,還有一個可選的方法stop()
礁哄,在Verticle停止的時候調(diào)用。
public class MyVerticle extends AbstractVerticle {
// Called when verticle is deployed
public void start() {
}
// Optional - called when verticle is undeployed
public void stop() {
}
}
如果需要異步Verticle溪北,繼承并實現(xiàn)方法簽名帶有Future
的那幾個方法即可桐绒。
使用JSON
Java中沒有對JSON的原生支持,所以Vertx首先就對這些數(shù)據(jù)類型進(jìn)行了支持之拨。
JSON對象
首先先來看看JSON對象茉继。我們可以由字符串創(chuàng)建JSON對象。
String jsonString = "{\"name\":\"yitian\",\"age\":25}";
JsonObject jsonObject = new JsonObject(jsonString);
System.out.println(jsonObject);
也可以由Map來創(chuàng)建Json對象蚀乔。
Map<String, Object> map = new HashMap<>();
map.put("name", "yitian");
map.put("age", 25);
JsonObject jsonObject2 = new JsonObject(map);
System.out.println(jsonObject2);
當(dāng)然也可以直接創(chuàng)建JsonObj對象烁竭。JsonObject的默認(rèn)構(gòu)造函數(shù)會創(chuàng)建一個空J(rèn)son對象,然后我們可以向其中填充數(shù)據(jù)吉挣。這個對象支持流式操作颖变,所以可以直接把多個put
方法連續(xù)調(diào)用。
JsonObject jsonObject3 = new JsonObject();
jsonObject3.put("name", "yitian").put("age", 25);
System.out.println(jsonObject3);
如果要獲取Json對象的屬性值也很簡單听想,調(diào)用相應(yīng)的getXXX
方法即可腥刹。
String name = jsonObject.getString("name");
int age = jsonObject.getInteger("age");
System.out.printf("name:%s,age:%d", name, age);
Json對象也可以和Java實體類之間通過mapTo
和mapFrom
互轉(zhuǎn)。
User user = jsonObject.mapTo(User.class);
System.out.println(user);
JsonObject userObject = JsonObject.mapFrom(user);
System.out.println(userObject);
最后汉买,Json對象也可以轉(zhuǎn)換為字符串衔峰,只需要調(diào)用encode
方法即可。如果查看源代碼可以發(fā)現(xiàn)JsonObject
的toString
方法也調(diào)用了encode
方法蛙粘,所以通過toString
方法也可以轉(zhuǎn)為字符串(不過有點(diǎn)多此一舉的意思)垫卤。
String stringValue = jsonObject.encode();
System.out.println(stringValue);
JSON數(shù)組
如果要創(chuàng)建Json數(shù)組,使用JsonArray
類出牧。它的使用方法和JsonObject類似穴肘。
JsonArray jsonArray = new JsonArray();
jsonArray.add("yitian").add(25).add(true);
System.out.println(jsonArray);
System.out.println(jsonArray.encode());
//獲取Json數(shù)組的元素
String name = jsonArray.getString(0);
int age = jsonArray.getInteger(1);
System.out.printf("name:%s,age:%d", name, age);
Buffer
Buffer是Vertx中通用的一種傳遞數(shù)據(jù)的方式,所以先來介紹一下它舔痕。
創(chuàng)建Buffer
有以下幾種創(chuàng)建Buffer的方式评抚。如果預(yù)先知道需要數(shù)據(jù)的大小,可以使用最后一種方式伯复,在創(chuàng)建的同時指定Buffer的大小慨代。
//創(chuàng)建空Buffer
Buffer buffer1 = Buffer.buffer();
Buffer buffer2 = Buffer.buffer(new byte[]{1, 2, 3, 4, 5});
Buffer buffer3 = Buffer.buffer("abcde");
Buffer buffer4 = Buffer.buffer("一二三四五", "utf-8");
//創(chuàng)建帶初始大小的Buffer
Buffer buffer5 = Buffer.buffer(1024);
寫入Buffer
有兩種寫入Buffer的方式,追加寫入(appendXXX)和隨機(jī)寫入(setXXX)啸如,這些方法對于各種常用類型做了重載侍匙,可以滿足我們各種需求。顧名思義叮雳,追加寫入會將數(shù)據(jù)寫入Buffer的最后想暗;隨機(jī)寫入可以修改Buffer任何位置的數(shù)據(jù)妇汗。Buffer可以自動擴(kuò)容,所以不必?fù)?dān)心出現(xiàn)IndexOutOfBoundsException
说莫。
Buffer buffer = Buffer.buffer();
//追加寫入方式
buffer.appendString("some text");
//隨機(jī)寫入方式
buffer.setString(10, "abcde");
讀取Buffer
從Buffer讀取數(shù)據(jù)使用getXXX
方法即可铛纬。
//讀取數(shù)據(jù)
for (int i = 0; i < buffer.length(); ++i) {
System.out.print(buffer.getShort(i));
}
當(dāng)把Buffer提交到網(wǎng)絡(luò)套接字等目的地后,Buffer就不能被重用了唬滑。
TCP服務(wù)端和客戶端
TCP服務(wù)端
首先需要使用Vertx對象創(chuàng)建一個TCP服務(wù)器告唆。
NetServer server = vertx.createNetServer();
如果需要配置服務(wù)器的屬性,可以在創(chuàng)建的時候傳遞一個NetServerOptions
類型參數(shù)晶密。
NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);
要讓服務(wù)器開始監(jiān)聽擒悬,使用下面代碼即可。
NetServer server = vertx.createNetServer();
server.listen();
當(dāng)然也可以在監(jiān)聽的時候指定端口號等屬性稻艰,這時候會覆蓋前面設(shè)置的NetServerOptions
屬性懂牧。默認(rèn)地址是0.0.0.0
,表示監(jiān)聽所有可用的地址尊勿,默認(rèn)端口號是0
僧凤,表示隨機(jī)選取一個可用的端口號。
NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");
如果希望及時獲取服務(wù)器監(jiān)聽的結(jié)果元扔,可以使用下面的形式躯保,通過lambda表達(dá)式來及時得知監(jiān)聽成功與否。
NetServer server = vertx.createNetServer();
server.listen(1234, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Server is now listening!");
} else {
System.out.println("Failed to bind!");
}
});
如果使用隨機(jī)端口號澎语,那么需要在監(jiān)聽成功之后獲取TCP服務(wù)器使用的端口號途事。
NetServer server = vertx.createNetServer();
server.listen(0, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Server is now listening on actual port: " + server.actualPort());
} else {
System.out.println("Failed to bind!");
}
});
如果要從套接字獲取數(shù)據(jù),需要以下的代碼擅羞。
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
socket.handler(buffer -> {
System.out.println("I received some bytes: " + buffer.length());
});
});
如果要向套接字獲取寫入數(shù)據(jù)尸变,可以利用前面介紹的Buffer。需要注意减俏,一旦將Buffer寫入套接字召烂,那么Buffer將會失效,無法重用娃承。
Buffer buffer = Buffer.buffer().appendFloat(12.34f).appendInt(123);
socket.write(buffer);
// Write a string in UTF-8 encoding
socket.write("some data");
// Write a string using the specified encoding
socket.write("some data", "UTF-16");
最后奏夫,要關(guān)閉服務(wù)器,調(diào)用close
方法草慧。當(dāng)然也可以檢查關(guān)閉結(jié)果桶蛔。
server.close(res -> {
if (res.succeeded()) {
System.out.println("Server is now closed");
} else {
System.out.println("close failed");
}
});
前面我們使用了connectHandler
來讀取套接字傳遞來的數(shù)據(jù)匙头,當(dāng)然還有幾個Handler可供使用漫谷。closeHandler
在服務(wù)器關(guān)閉的時候通知我們,而exceptionHandler
會將所有異常報告給我們蹂析。
TCP客戶端
要創(chuàng)建TCP客戶端很簡單舔示。
NetClient client = vertx.createNetClient();
類似地碟婆,也可以在創(chuàng)建的時候指定配置。
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
創(chuàng)建客戶端之后惕稻,需要和服務(wù)器進(jìn)行連接竖共。
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
client.connect(4321, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Connected!");
//用Socket操作數(shù)據(jù)
NetSocket socket = res.result();
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
HTTP服務(wù)器和客戶端
HTTP服務(wù)器
創(chuàng)建HTTP服務(wù)器很簡單。
HttpServer server = vertx.createHttpServer();
如果需要指定配置俺祠,也很容易公给。
HttpServerOptions options = new HttpServerOptions().setMaxWebsocketFrameSize(1000000);
HttpServer server = vertx.createHttpServer(options);
創(chuàng)建服務(wù)器之后,還需要監(jiān)聽端口蜘渣。默認(rèn)地址是0.0.0.0
淌铐,默認(rèn)端口號是80
。
HttpServer server = vertx.createHttpServer();
server.listen();
//監(jiān)聽指定端口
server.listen(8080, "myhost.com");
如果要確定是否監(jiān)聽成功蔫缸,可以使用下面的代碼腿准。
HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com", res -> {
if (res.succeeded()) {
System.out.println("Server is now listening!");
} else {
System.out.println("Failed to bind!");
}
});
要處理發(fā)送過來的HTTP請求,使用requestHandler
拾碌。Handler內(nèi)部的request
參數(shù)有很多屬性和方法可以幫助我們獲取相應(yīng)的數(shù)據(jù)吐葱。熟悉Java Servlet編程的同學(xué)應(yīng)該會感到很親切。這里就不詳細(xì)介紹了校翔。
server.requestHandler(request -> {
// 在這里編寫代碼
});
要返回響應(yīng)弟跑,需要Response對象。
HttpServerResponse response = request.response();
response.write(buffer);
//直接返回字符串也可以
response.write("hello world!");
//輸出完響應(yīng)之后需要關(guān)閉相應(yīng)流
response.end();
如果要指定返回的header防症、content-type等信息窖认,可以用下面的代碼。
HttpServerResponse response = request.response();
MultiMap headers = response.headers();
headers.set("content-type", "text/html");
headers.set("other-header", "wibble");
或者直接使用putHeaders
方法告希。
response.putHeader("content-type", "text/html").putHeader("other-header", "wibble");
Vertx還有一些特性扑浸,可以幫助我們處理文件上傳等情況,不過篇幅所限就不介紹了燕偶。
HTTP客戶端
要創(chuàng)建HTTP客戶端很簡單喝噪。
HttpClient client = vertx.createHttpClient();
如果要增加配置,可以這樣指么。
HttpClientOptions options = new HttpClientOptions().setKeepAlive(false);
HttpClient client = vertx.createHttpClient(options);
如果要發(fā)起請求酝惧,調(diào)用客戶端的相應(yīng)方法即可。
Vertx vertx = Vertx.vertx();
HttpClient client = vertx.createHttpClient();
client.getNow("httpbin.org", "/get", response -> {
response.bodyHandler(System.out::println);
});
由于篇幅所限伯诬,這里只介紹Vert.x 核心包的一些功能晚唇,如果想了解更多信息,請直接查看官方文檔盗似。