是一個事件驅動的JVM上的架構,可以幫助我們建構現代、靈活、可擴充的程式。Vert.x有多種語言的版本,可以用在Java、Kotlin、Scala、Groovy、Ruby等語言上。當然現在讨論的是如何在Java上使用Vert.x。
Vert.x是一個比較大的架構,包含了各個方面的功能。是以我決定寫幾篇文章,分别來介紹這些功能。是以今天先來看看Vert.x最核心的一些功能吧,這些功能都在vertx-core包下。官方的英文文檔在
這裡,本文參考和引用了Vertx官方文檔上的一些内容,如果需要詳細資訊請直接看官方文檔。當然我又發現了志願者翻譯的
中文文檔,品質也可以,隻不過版本稍微落後一些。
Vert.x核心庫包含了以下一些功能,它們都是比較底層的功能,開發者可以根據需要使用。當然由于Vert.x的功能很多,是以這裡我不打算全部介紹,隻準備介紹一些比較常用的功能。如果想了解全部功能的話,還是請參考官方文檔。
- TCP用戶端和服務端
- HTTP用戶端和服務端以及WebSockets支援
- 事件總線
- 共享資料,包括本地maps以及分布式聚簇maps
- 周期性和延遲操作
- 資料報
- DNS用戶端
- 檔案系統通路
- 高可用性
- 聚簇
Vert.x的特點是事件驅動、流式程式設計和非阻塞,這些特點将會在後面逐一介紹。
引入依賴
如果使用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'
}
開始使用
建立Vertx對象
要使用Vertx的第一步就是建立Vertx對象,所有API都要通過這個對象來調用。一般情況下,一個程式隻需要一個Vertx對象即可,不過有時候為了程式隔離等原因,我們會需要多個Vertx對象。建立Vertx對象很簡單,調用下面一行代碼即可。
Vertx vertx = Vertx.vertx();
有時候可能需要對Vertx進行一些配置,可以通過添加程式參數來實作。
VertxOptions options = new VertxOptions();
options.setWorkerPoolSize(20);
Vertx vertx2 = Vertx.vertx(options);
Verticles
Verticles是Vertx中的一個模型,可以幫助我們封裝代碼。Verticles是一個可選的模型,是以即使我們不使用Verticles,也可以繼續使用Vertx。Verticles說起來很簡單,就是一個接口。當然實際情況下,一般都是繼承
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
抽象類之後,必須實作的方法是
start()
,它會在Verticle部署的時候調用,還有一個可選的方法
stop()
,在Verticle停止的時候調用。
public class MyVerticle extends AbstractVerticle {
// Called when verticle is deployed
public void start() {
}
// Optional - called when verticle is undeployed
public void stop() {
}
}
如果需要異步Verticle,繼承并實作方法簽名帶有
Future
的那幾個方法即可。
使用JSON
Java中沒有對JSON的原生支援,是以Vertx首先就對這些資料類型進行了支援。
JSON對象
首先先來看看JSON對象。我們可以由字元串建立JSON對象。
String jsonString = "{\"name\":\"yitian\",\"age\":25}";
JsonObject jsonObject = new JsonObject(jsonString);
System.out.println(jsonObject);
也可以由Map來建立Json對象。
Map<String, Object> map = new HashMap<>();
map.put("name", "yitian");
map.put("age", 25);
JsonObject jsonObject2 = new JsonObject(map);
System.out.println(jsonObject2);
當然也可以直接建立JsonObj對象。JsonObject的預設構造函數會建立一個空Json對象,然後我們可以向其中填充資料。這個對象支援流式操作,是以可以直接把多個
put
方法連續調用。
JsonObject jsonObject3 = new JsonObject();
jsonObject3.put("name", "yitian").put("age", 25);
System.out.println(jsonObject3);
如果要擷取Json對象的屬性值也很簡單,調用相應的
getXXX
方法即可。
String name = jsonObject.getString("name");
int age = jsonObject.getInteger("age");
System.out.printf("name:%s,age:%d", name, age);
Json對象也可以和Java實體類之間通過
mapTo
和
mapFrom
互轉。
User user = jsonObject.mapTo(User.class);
System.out.println(user);
JsonObject userObject = JsonObject.mapFrom(user);
System.out.println(userObject);
最後,Json對象也可以轉換為字元串,隻需要調用
encode
方法即可。如果檢視源代碼可以發現
JsonObject
的
toString
方法也調用了
encode
方法,是以通過
toString
方法也可以轉為字元串(不過有點多此一舉的意思)。
String stringValue = jsonObject.encode();
System.out.println(stringValue);
JSON數組
如果要建立Json數組,使用
JsonArray
類。它的使用方法和JsonObject類似。
JsonArray jsonArray = new JsonArray();
jsonArray.add("yitian").add(25).add(true);
System.out.println(jsonArray);
System.out.println(jsonArray.encode());
//擷取Json數組的元素
String name = jsonArray.getString(0);
int age = jsonArray.getInteger(1);
System.out.printf("name:%s,age:%d", name, age);
Buffer
Buffer是Vertx中通用的一種傳遞資料的方式,是以先來介紹一下它。
建立Buffer
有以下幾種建立Buffer的方式。如果預先知道需要資料的大小,可以使用最後一種方式,在建立的同時指定Buffer的大小。
//建立空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");
//建立帶初始大小的Buffer
Buffer buffer5 = Buffer.buffer(1024);
寫入Buffer
有兩種寫入Buffer的方式,追加寫入(appendXXX)和随機寫入(setXXX),這些方法對于各種常用類型做了重載,可以滿足我們各種需求。顧名思義,追加寫入會将資料寫入Buffer的最後;随機寫入可以修改Buffer任何位置的資料。Buffer可以自動擴容,是以不必擔心出現
IndexOutOfBoundsException
。
Buffer buffer = Buffer.buffer();
//追加寫入方式
buffer.appendString("some text");
//随機寫入方式
buffer.setString(10, "abcde");
讀取Buffer
從Buffer讀取資料使用
getXXX
//讀取資料
for (int i = 0; i < buffer.length(); ++i) {
System.out.print(buffer.getShort(i));
}
當把Buffer送出到網絡套接字等目的地後,Buffer就不能被重用了。
TCP服務端和用戶端
TCP服務端
首先需要使用Vertx對象建立一個TCP伺服器。
NetServer server = vertx.createNetServer();
如果需要配置伺服器的屬性,可以在建立的時候傳遞一個
NetServerOptions
類型參數。
NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);
要讓伺服器開始監聽,使用下面代碼即可。
NetServer server = vertx.createNetServer();
server.listen();
當然也可以在監聽的時候指定端口号等屬性,這時候會覆寫前面設定的
NetServerOptions
屬性。預設位址是
0.0.0.0
,表示監聽所有可用的位址,預設端口号是
,表示随機選取一個可用的端口号。
NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");
如果希望及時擷取伺服器監聽的結果,可以使用下面的形式,通過lambda表達式來及時得知監聽成功與否。
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!");
}
});
如果使用随機端口号,那麼需要在監聽成功之後擷取TCP伺服器使用的端口号。
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!");
}
});
如果要從套接字擷取資料,需要以下的代碼。
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
socket.handler(buffer -> {
System.out.println("I received some bytes: " + buffer.length());
});
});
如果要向套接字擷取寫入資料,可以利用前面介紹的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");
最後,要關閉伺服器,調用
close
方法。當然也可以檢查關閉結果。
server.close(res -> {
if (res.succeeded()) {
System.out.println("Server is now closed");
} else {
System.out.println("close failed");
}
});
前面我們使用了
connectHandler
來讀取套接字傳遞來的資料,當然還有幾個Handler可供使用。
closeHandler
在伺服器關閉的時候通知我們,而
exceptionHandler
會将所有異常報告給我們。
TCP用戶端
要建立TCP用戶端很簡單。
NetClient client = vertx.createNetClient();
類似地,也可以在建立的時候指定配置。
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
建立用戶端之後,需要和伺服器進行連接配接。
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
client.connect(4321, "localhost", res -> {
if (res.succeeded()) {
System.out.println("Connected!");
//用Socket操作資料
NetSocket socket = res.result();
} else {
System.out.println("Failed to connect: " + res.cause().getMessage());
}
});
HTTP伺服器和用戶端
HTTP伺服器
建立HTTP伺服器很簡單。
HttpServer server = vertx.createHttpServer();
如果需要指定配置,也很容易。
HttpServerOptions options = new HttpServerOptions().setMaxWebsocketFrameSize(1000000);
HttpServer server = vertx.createHttpServer(options);
建立伺服器之後,還需要監聽端口。預設位址是
0.0.0.0
,預設端口号是
80
HttpServer server = vertx.createHttpServer();
server.listen();
//監聽指定端口
server.listen(8080, "myhost.com");
如果要确定是否監聽成功,可以使用下面的代碼。
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!");
}
});
要處理發送過來的HTTP請求,使用
requestHandler
。Handler内部的
request
參數有很多屬性和方法可以幫助我們擷取相應的資料。熟悉Java Servlet程式設計的同學應該會感到很親切。這裡就不詳細介紹了。
server.requestHandler(request -> {
// 在這裡編寫代碼
});
要傳回響應,需要Response對象。
HttpServerResponse response = request.response();
response.write(buffer);
//直接傳回字元串也可以
response.write("hello world!");
//輸出完響應之後需要關閉相應流
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用戶端
要建立HTTP用戶端很簡單。
HttpClient client = vertx.createHttpClient();
如果要增加配置,可以這樣。
HttpClientOptions options = new HttpClientOptions().setKeepAlive(false);
HttpClient client = vertx.createHttpClient(options);
如果要發起請求,調用用戶端的相應方法即可。
Vertx vertx = Vertx.vertx();
HttpClient client = vertx.createHttpClient();
client.getNow("httpbin.org", "/get", response -> {
response.bodyHandler(System.out::println);
});
由于篇幅所限,這裡隻介紹Vert.x 核心包的一些功能,如果想了解更多資訊,請直接檢視官方文檔。