一、Vertx是什麼?
github: https://github.com/vert-x3
官網:http://vertx.io/
Vert.x誕生於2011年,當時叫node.x,不過後來因爲某些原因改名爲Vert.x,目前官網最新版本是4.4.0,官網上介紹Vert.x是一個用於在JVM上構建高效應用程序的工具包,是JVM上的Reative開發套件。說白了,Vert.x就是一堆的jar包,提供了一系列的編程API接口,通過這些API可以實現異步編程。
Vert.x目前是我見過功能最強大、對第三方庫依賴最少的Java框架,是一個異步無阻塞的網絡框架,其參照物是node.js,基本上node.js能幹的事情,Vert.x都能幹,它只依賴Netty4以及Jacskon,Vert.x利用Netty4的EventLoop來做單線程的事件循環,所以跑在Vert.x上的業務不能做CPU密集型的運算,這樣會導致整個線程被阻塞,另外如果你需要建立分佈式的Vert.x,則再依賴HazelCast這個分佈式框架即可,注意Vert.x3必須基於Java8。由於基於JVM,所以Vert.x可以用其他語言來實現你的業務。
異步編程怎麼理解呢?對於寫過ajax的人來說,不難理解,$.ajax方法並不會阻塞,而是直接向下執行,等到遠程服務器響應之後,纔會回調success方法,那麼這時候success方法纔會執行。ajax下面的代碼不會等到success方法執行完畢之後再執行,這就是所謂的異步。如下:
console.log("1");
$.ajax({
"url" : "/hello",
"type" : "post",
"dataType" : "json",
"success" : function(val) {
console.log("2");
}
});
console.log("3");
瀏覽器會先輸出1,在hello方法沒有相應之前不會輸出2,但是會先輸出3,等接口響應success之後會輸出2,這就是異步,異步編程是Vert.x的一大特性,也是Vert.x的核心,Vert.x可以開發Web應用,但Vert.x不僅僅是一個Web開發框架,他更像Spring,是一個技術棧(Vert.x生態可以查看https://github.com/vert-x3/vertx-awesome),或者說是一個Vert.x生態體系。在這個體系中,Vert.x只是提供了Web開發的能力。下面對Vertx和Spring做一個對比:
項目 | Spring | Vertx |
---|---|---|
核心框架 | spring-core | vertx-core |
Web開發 | spring-webmvc | vertx-web |
jdbc框架 | spring-jdbc | vertx-jdbc-client |
redis | spring-data-redis | vertx-redis-client |
微服務 | spring-cloud | vertx-hazelcast |
可以說,很多spring能做的事情,Vertx也都能實現。那麼既然如此,Spring如此強大,社區如此活躍,爲何還會有Vertx呢?他們之前區別的核心點就只有一個:Spring的操作是同步的,Vertx的操作是異步的。異步帶來了更高的性能,但同時也帶來了編碼和調試的複雜度,但不得不說異步可能是未來的一個趨勢,至少在Java實現高性能服務器上的一個趨勢。
在Java領域,做Web開發我們一般有很多的選擇,比如使用原生的Servlet,比如使用SpringMVC,再比如使用Struts等等總之你有很多的選擇。在國內,目前來講,SpringMVC作爲Spring體系下的Web層架構,是深受企業青睞的,絕大部分的企業可能都在使用SpringMVC,而對於我們今天要說的Vert.x這個Web層框架,卻很少有人知道,但它卻是僅次於SpringMVC,排名第二的一個Web層框架。
Vert.x是基於事件的,提供一個事件驅動編程模型,使用Vert.x作爲服務器時,程序員只要編寫事件處理器event handler即可,當TCP socket有數據時,event handler理解被創建調用,另外它還可以在以下幾種情況激活: ‘當事件總線Event Bus接受到消息時,’ ‘當接收到HTTP消息時,’ 當一個連接斷開時’,’ ‘當計時器超時時.’
作爲服務層處理邏輯這一層基本上對應的傳統Java裏的領域模型處理層,各種Service調用以及對數據層的調用,差不多是一個承上啓下的一層,傳統的模型裏,這一層基本上都是同步調用,即使有異步調用,也是與業務邏輯分離的異步,如果全異步會導致業務邏輯碎亂,代碼很難描述清楚,到這裏你會發現Vert.x其實不太好融合到業務性很強的服務層裏,其主要原因如下
1、自身是異步體系,不適合描述順序邏輯性強的業務。
2、由於異步的問題,訪問數據層也必須是異步,導致業務模型進一步碎片化。
二、Vertx基本概念
我們學習一個新東西,都要去了解理解它的基本概念,學習Vert.x也是如此。那麼Vert.x又有哪些基本概念呢?
- Verticle:Vert.x的執行單元,即程序的入口,它可以用JS、Ruby、Java等多語言來編寫,每個語言可能實現的方式不一樣,比如Java需要繼承一個AbstractVerticle抽象類。在同一個Vert.x實例中可以同時執行多個Verticle,一個應用可能由多個Verticle組成,它們被部署到不同的網絡節點上,彼此之間通過在Vert.x的事件總線(event bus)上交換信息來通信。
- Module:Vert.x應用由一個或者多個modules來實現,一個module由多個verticles來實現,所以可以把module理解爲Java package,裏面可能是特定業務的實現或者公共的服務實現(那些可以重用的服務),Vert.x編寫好的module可以發佈到maven的倉庫裏,以zip包裝成二進制格式,或者發佈到vert.x module 註冊中心,實際上這種以模塊方式的開發,支撐着整個Vert.x生態系統。
- Event Loops:事件循環,是由Vert.x啓動的事件處理線程,也是Vert.x項目對外開放的入口,Vert.x由此接收請求事件,一個Vert.x由一個或者多個事件循環線程組成,線程最大數爲主機有效的CPU核數。處理函數是handler接口,用來處理事件。
- Event Loop verticle:事件的業務處理線程,存在於Event Loop中,用於處理非阻塞短任務。
- Event bus:是Vert.x的核心,在集羣中容器之間的通信,各個Verticle之間的通信也都是經過Event bus來實現的,所以Event bus存在堵塞的可能。
- Shared Data(共享數據):消息通過bus可以在各個Vert.x實例直接傳輸,但是如果多個Verticle在一個Vert.x實例內,是可以避免進行消息傳輸的,比如單個JVM內,你不會通過兩個socket互相在兩個Java對象之間傳輸消息的,這是同樣的道理,但是因爲實例隔離,因爲actor模型,所以對象數據如果要傳輸到handler裏必須通過消息傳輸。Shared Data是Vert.x提供的一個簡單共享map和set,用來解決各個Verticle之間的數據共享的,數據被存儲到一個不可變的數據結構裏,各個實例可以直接通過此API獲取數據。
- Worker Verticle(阻塞處理):事件處理之外肯定會發生長時間數據處理請求,比如處理一個圖片上傳,然後轉存到磁盤等,此時就需要事件的業務處理線程用來處理長時間阻塞任務,worker verticle就是這個阻塞處理線程,是一種阻塞式的方法,但是無法做到併發水平擴展,在Verticle類型中有一種特別的verticle叫做worker,與標準的verticle不同的是它採用的是vert.x內部的另外一種線程池叫做worker pool,而不是使用event loop ,worker verticle不會並行的執行handler,而是阻塞式的,等前一個handler處理完了纔會執行後面的請求,這種的適合CPU密集型的,標準的verticle適合IO密集型的。vert.x爲了支持標準的和阻塞的worker verticle提供了一種混合線程模型,你可以根據自己的需求按需選擇合適的線程模型。
純文字的,可能沒什麼概念,下面我們來看一下Vertx的框架圖,加深印象,便於理解:
三、Vertx能幹什麼?
一句話總結:Java能做的,Vert.x都能做。咱這裏主要列出來Vert.x擅長做什麼?
- Web開發,Vert.x封裝了Web開發常用的組件,支持路由、Session管理、模板等,可以非常方便的進行Web開發。
不需要容器!不需要容器!不需要容器!
- TCP/UDP開發,Vert.x底層基於Netty,提供了豐富的IO類庫,支持多種網絡應用開發。
不需要處理底層細節(如拆包和粘包),注重業務代碼編寫。
- 提供對WebSocket的支持,可以做網絡聊天室,動態推送等。
- Event Bus(事件總線)是Vert.x的神經系統,通過Event Bus可以實現分佈式消息,遠程方法調用等等。正是因爲Event Bus的存在,Vert.x可以非常便捷的開發
微服務
應用。 - 支持主流的數據和消息的訪問redis mongodb rabbitmq kafka 等。
- 分佈式鎖,分佈式計數器,分佈式map的支持。
四、Vertx的技術體系
上面也提到了,Vert.x和Spring一樣,也有着完善的生態,具體可以查看https://github.com/vert-x3/vertx-awesome 我們可以看到,每一塊內容都提供了多種的實現,有官方支持的版本還有社區版本。下面我們具體介紹下技術體系中官方支持的版本。
- 核心模塊。Vert.x核心模塊包含一些基礎的功能,如HTTP,TCP,文件系統訪問,EventBus、WebSocket、延時與重複執行、緩存等其他基礎的功能,你可以在你自己的應用程序中直接使用。可以通過vertx-core模塊引用即可。
- web模塊。Vert.x Web是一個工具集,雖然核心模塊提供了HTTP的支持,但是要開發複雜的Web應用,還需要路由、Session、請求數據讀取、Rest支持等等還需要Web模塊,這裏提供了上述的這些功能的API,便於開發,除了對Web服務的開發以外,還提供了對Web客戶端請求的支持,通過vertx-web-client即可方便的訪問HTTP服務。有朋友可能會有疑惑,我明明可以使用JDK提供的URL來請求HTTP服務啊,使用Vert.x一定要注意,Vert.x是一個異步框架,請求HTTP服務是一個耗時操作,所有的耗時,都會阻塞Event Bus,導致整體性能被拖垮,因此,對於請求Web服務,一定要使用Vert.x提供的vertx-web-client模塊。
- 數據訪問模塊:Vert.x提供了對關係型數據庫、NoSQL、消息中間件的支持,傳統的客戶端因爲是阻塞的,會嚴重影響系統的性能,因此Vert.x提供了對以上客戶端的異步支持。具體支持的數據訪問如下:MongoDB client,JDBC client,SQL common,Redis client,MySQL/PostgreSQLclient。
- Reactive響應式編程:複雜的異步操作會導致異步回調地獄的產生,看下面的代碼,這是我在Vert.x提供的例子中找到的,我們不去管這段代碼幹了啥,只是看後面的}就很驚訝了,如果操作更爲複雜一些,會嵌套的層次更多,通過reactive可以最小化的簡化異步回調地獄。
// create a test table
execute(conn.result(), "create table test(id int primary key, name varchar(255))", create -> {
// start a transaction
startTx(conn.result(), beginTrans -> {
// insert some test data
execute(conn.result(), "insert into test values(1, 'Hello')", insert -> {
// commit data
rollbackTx(conn.result(), rollbackTrans -> {
// query some data
query(conn.result(), "select count(*) from test", rs -> {
for (JsonArray line : rs.getResults()) {
System.out.println(line.encode());
}
// and close the connection
conn.result().close(done -> {
if (done.failed()) {
throw new RuntimeException(done.cause());
}
});
});
});
});
});
});
再看一個使用Reactive2構建的多步操作的代碼,paramCheckStep,insertPayDtlStep,requestStep等等都是異步方法,但這裏就很好的處理了異步回調的問題,不再有那麼多層的大括號,代碼結構也geng
public void scanPay(JsonObject data, Handler<AsyncResult<JsonObject>> resultHandler) {
paramCheckStep(data) // 參數校驗
.flatMap(this::insertPayDtlStep) // 插入流水
.flatMap(x -> requestStep(x, config)) // 請求上游
.flatMap(this::cleanStep) //參數清理
.subscribe(ok -> {
logger.info("成功結束");
resultHandler.handle(Future.succeededFuture(ok));
},
err -> {
logger.error("正在結束", err);
resultHandler.handle(Future.failedFuture(err));
}
);
}
- 整合其他模塊。
- 郵件客戶端。Vert.x提供了一簡單STMP郵件客戶端,所以你可以在應用程序中發送電子郵件。
- STOMP客戶端與服務端。Vert.x提供了STOMP協議的實現包括客戶端與服務端。
- Consul Client。consul是google開源的一個使用go語言開發的服務發現、配置管理中心服務。內置了服務註冊與發現框 架、分佈一致性協議實現、健康檢查、Key/Value存儲、多數據中心方案。
- RabbitMQ Client Kafka Client。消息隊裏的客戶端支持。
- JCA適配器。Vert.x提供了Java連接器架構適配器,這允許同任意JavaEE應用服務器進行互操作。
- 認證與授權:Vert.x提供了簡單API用於在應用中提供認證和授權。
- Auth common 通用的認證API,可以通過重寫AuthProvider類來實現自己的認證。
- JDBC auth 後臺爲JDBC的認證實現。
- JWT auth 用JSON Web tokens認證實現。
- Shiro auth 使用Apache Shiro認證實現。
- MongoDB auth MongoDB認證實現。
- OAuth 2 Oauth2協義認證實現。
- htdigest auth 這個是新增一種認證的支持
- 微服務:Vert.x提供多個組件構建基於微服務的應用程序。比如服務發現(Vert.x Service Discovery)、斷路器(Vert.x Circuit Breaker)、配置中心(Vert.x Config)等。
五、快速體驗:搭建一個簡單的Vertx項目並輸出Hello World
首先新建一個Maven項目,然後在pom.xml中加入相關的依賴和插件,如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>vertxone</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>4.0.0</vertx.version>
<main.class>org.example.Main</main.class>
</properties>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${main.class}</Main-Class>
</manifestEntries>
</transformer>
</transformers>
<artifactSet/>
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-prod.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
跟其它Maven項目一樣,我們首先定義了項目的GroupId,ArtifactId以及版本號,隨後我們定義了兩個屬性,分別是:vertx.version,也就是Vert.x的版本號,此處我們使用最新的Vert.x版本,也就是4.4.0;以及main.class,也就是我們要使用的包含有main函數的主類。之後我們引入了兩個Maven插件,分別是maven-compiler-plugin和maven-shade-plugin,前者用來將.java的源文件編譯成.class的字節碼文件,後者可將編譯後的.class字節碼文件打包成可執行的jar文件,俗稱fat-jar。
然後我們在src/main/java/org/example目錄下新建兩個java文件,分別是Main.java和MyFirstVerticle.java,代碼如下:
Main.java
package org.example;
import io.vertx.core.Vertx;
/**
* @author dxm
* @description
* @ClassName
* @date 2023-03-14 15:19
*/
public class Main {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(MyFirstVerticle.class.getName());
}
}
MyFirstVerticle.java
package org.example;
import io.vertx.core.AbstractVerticle;
/**
* @author dxm
* @description
* @ClassName
* @date 2023-03-14 15:25
*/
public class MyFirstVerticle extends AbstractVerticle {
public void start() {
vertx.createHttpServer().requestHandler(req -> {
req.response()
.putHeader("content-type", "text/plain")
.end("Hello World222!");
}).listen(8080);
}
}
然後用Maven的mvn package命令打包,隨後在src的同級目錄下會出現target目錄,進入之後,會出現vertxone-1.0-SNAPSHOT.jar和vertxone-1.0-SNAPSHOT-prod.jar兩個jar文件,後者是可執行文件,在有圖形界面的操作系統中,您可雙擊執行或者用以下命令:java -jar vertxone-1.0-SNAPSHOT-prod.jar執行,然後打開瀏覽器,在瀏覽器的地址欄中輸入:http://localhost:8080/ 便可看到熟悉的Hello World!啦。
我們也可以使用Launcher來替代Main類,這也是官方推薦的方式,在pom.xml中加入main.verticle屬性,並將該屬性值設置爲maven-shade-plugin插件的manifestEntries的Main-Verticle對應的值,最後修改main.class爲io.vertx.core.Launcher,修改後的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>vertxone</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>4.0.0</vertx.version>
<main.class>io.vertx.core.Launcher</main.class>
<main.verticle>org.example.MainVerticle</main.verticle>
</properties>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${main.class}</Main-Class>
<Main-Verticle>${main.verticle}</Main-Verticle>
</manifestEntries>
</transformer>
</transformers>
<artifactSet/>
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-prod.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
然後在src/main/java/org/example目錄下新增MainVerticle.java文件,代碼如下:
package org.example;
import io.vertx.core.AbstractVerticle;
/**
* @author dxm
* @description
* @ClassName
* @date 2023-03-14 3:48 PM
*/
public class MainVerticle extends AbstractVerticle {
public void start() throws Exception {
vertx.deployVerticle(MyFirstVerticle.class.getName());
}
}
然後重新打包後執行,便可再次看到Hello World!。
六、單元測試
下面我們將會介紹測試部分,首先引入兩個新的測試依賴,修改後的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>vertxone</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>4.0.0</vertx.version>
<main.class>io.vertx.core.Launcher</main.class>
<main.verticle>org.example.MainVerticle</main.verticle>
</properties>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-unit</artifactId>
<version>${vertx.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${main.class}</Main-Class>
<Main-Verticle>${main.verticle}</Main-Verticle>
</manifestEntries>
</transformer>
<!--多語言支持在打包時需加入以下轉換器-->
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
</transformer>
</transformers>
<artifactSet/>
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-prod.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
隨後在src/test/java/org/example目錄下新增MyFirstVerticleTest.java文件:
package org.example;
import io.vertx.core.Vertx;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* @author dxm
* @description
* @ClassName
* @date 2023-03-14 3:56 PM
*/
@RunWith(VertxUnitRunner.class)
public class MyFirstVerticleTest {
private Vertx vertx;
@Before
public void setUp(TestContext context){
vertx = Vertx.vertx();
vertx.deployVerticle(MyFirstVerticle.class.getName(), context.asyncAssertSuccess());
}
@After
public void tearDown(TestContext context) {
vertx.close(context.asyncAssertSuccess());
}
@Test
public void testApplication(TestContext context) {
final Async async = context.async();
vertx.createHttpClient().getNow(8080, "localhost", "/", response -> {
response.handler(body -> {
context.assertTrue(body.toString().contains("Hello"));
async.complete();
});
});
}
}
執行該測試案例便可得到期望的結果,有興趣的可以練習一下。
總結
最後呢,我想說咱程序員不能只會一種語言,開發框架也不能只知道Spring,也得去了解學習接觸一些其他的框架,拓展自己的知識面,在後面解決問題的時候,你就會發現,書到用時方恨少,所以還是要在日常進行不斷的積累和總結,然後在適當的場景下選擇合適的技術選型來解決問題,適合自己的纔是最好的,最後給大家佈置一個作業,就是結合自己現在的項目結構分層寫一下增刪改查的接口,感興趣的你,趕緊去試試吧。