使用Vert.x + SpringBoot編寫業務系統

這一期文章主要爲大家介紹如何將Vert.x與SpringBoot結合起來編寫最最最常見的業務系統,即數據庫增刪改查。

談兩句SpringBoot

SpringBoot大家都很熟了,一個快速開發框架,其最大的特點是可將Spring應用打成可執行jar包,從而不再依賴外部容器,如Tomcat。可能絕大多數人在使用SpringBoot時一定離不了嵌入式Tomcat, 從而造成了一想到SpringBoot就會將其與SpringMVC聯繫在一起的現象。其實我們可以只使用SpringBoot的一鍵執行和提供Spring環境的特性,Web層直接替換成Vert.x. 此外,一些短時執行的任務也可以用這種方式來寫,簡直不能更爽。

Vertx-web的請求路由

上一篇寫的Http Server是沒有路由的,所有的請求都會由同一個Handler處理。如果是隻提供一個簡單的API服務這樣是沒有問題的,但在實際業務系統中一般接口數量都比較多,這時候就需要一個路由組件來將不同的Path, Method映射到不同的handler上,使用方法如下:

HttpServer server = vertx.createHttpServer();
        Router router = Router.router(vertx); // (0)
        router.route("/a/b/c/path") // (1)
                .handler(BodyHandler.create()) // (2)
                .handler(demoHandler) // (3)
                .blockingHandler(blockHandler); // (4)
        
        server.requestHandler(router::accept) // (5)
                .listen(8080);

(0): 構造一個Router。

(1): 添加對/a/b/c/path的路由。這裏也可以使用重載的帶有HTTP Method的方法。

(2): 當收到path爲/a/b/c/path的請求時,先調用BodyHandler。這裏BodyHandler是Vert.x提供的處理器,只有在請求處理鏈路的開頭添加了此Handler我們才能在後續的Handler中拿到請求體。

(3): 添加我們的業務處理器, 此處理器會在NIO線程中執行。

(4): 添加含有阻塞調用的業務處理器,此處理器會在worker線程池中執行,不會block NIO線程。

(5): 將Router的Handler設爲整個HTTP Server的Handler。

其中Handler是一個函數式接口,定義如下:

@FunctionalInterface
public interface Handler<E> {

  /**
   * Something has happened, so handle it.
   *
   * @param event  the event to handle
   */
  void handle(E event);
}

可見,vertx添加請求處理器的過程非常簡單,而且寫起來也很爽。

不過這裏還是會有一個問題,我們的服務會有大量的Handler, 大量的路由,這時候就只能在這裏羅列一堆方法調用嗎?當然不用,這些重複性的工作一定是可以在框架層面處理掉的,比如,你可以選擇使用我寫的 spring-boot-starter-vertx, 用了以後就可以像SpringMVC裏@Controller註解一樣直接定義Handler了,starter會在啓動時自動掃描添加,像這樣:

@Component
// @BlockedHandler
@Slf4j
public class DemoHandler implements Handler<RoutingContext> {
    @Override
    public void handle(RoutingContext route) {
        log.info("invoke DemoHandler, path: {}", route.request().path());
        route.response().end("ok");
    }
}

如果Handler需要在worker線程池中調用,那麼只需要添加一個@BlockedHandler註解即可。

是不是很方便?

如何處理數據庫查詢

數據庫查詢操作其實代表的是阻塞調用。在Vert.x Web應用中,我們有兩種方式來處理block調用,一種就是像上面說的那樣,將含有block調用的代碼集中到一個Handler中然後註冊成blockingHandler; 另一種則是像node.js那樣使用回調。其中回調又可以分成兩類,一是使用 vert.x封裝好的異步JDBC,裏面所有的block操作都會多一個註冊回調方法的參數。這種方式我個人覺得如果你是剛剛入坑vert.x話那就很不推薦,因爲它會讓你對回調產生恐懼。這裏建議使用更傳統點的方法,即繼續使用你喜歡的持久化框架,只不過是在調用時將這部分代碼提交到worker線程池中執行。下面我們用代碼說話。

假設我們需要做兩次數據庫查詢,只有拿到兩次查詢的結果才能執行後面的邏輯,這種情況在業務系統中是非常常見的。用回調方式的代碼長這樣:

@Override
    public void handle(RoutingContext route) {
        log.info("invoke DemoHandler, path: {}", route.request().path());

        // (0)
        Future<String> fut1 = Future.future();
        Future<String> fut2 = Future.future();

        // 執行block調用
        route.vertx() // (1)
                .executeBlocking(
                        fut -> {
                            String result = demoService.blockingLogic(1); // (2)
                            fut.complete(result); // (3)
                        },
                        fut1.completer()  // (4)
                );

        // 執行block調用
        route.vertx()
                .executeBlocking(
                        fut -> {
                            String result = demoService.blockingLogic(2);
                            fut.complete(result);
                        },
                        fut2.completer()
                );

        // 組合結果
        CompositeFuture.all(fut1, fut2).setHandler(ar -> { // (5)
            if (!ar.succeeded()) { // (6)
                log.error("", ar.cause());
                route.response().end("error");
                return;
            }

            log.info("final step");

            List<String> resultList = ar.result().list(); // (7)
            route.response().end(resultList.toString());
        });
    }

(0): 構造兩個Future對象,用來協調、同步兩個異步調用

(1): 從一下文中獲取vertx對象,調用executeBlocking()方法向worker線程池中提交任務

(2): 調用會發生阻塞的數據庫查詢方法

(3): 調用傳進來的Future對象的complete()方法通知vert.x業務邏輯成功完成,並將執行的結果對象傳遞進來

(4): 將我們定義的Future對象標記爲成功或失敗。如果(2)或(3)拋出了異常,那麼fut2會被標記爲失敗,反之成功

(5): 使用CompositeFuture工具類的all()方法將上面定義的兩個Feature組合起來,並註冊一個回調方法,此方法會在fut1, fut2全部成功或有一個失敗時觸發。

(6): 回調觸發後,要先判斷結果是成功還是失敗

(7): 取出兩個DB查詢的結果

可以看到,業務邏輯被各種回調分散到了不同的Handler中。這就是使用異步框架最主要的工程代價。

組合起來

我寫了一個Demo web項目,它基於上面提到的 spring-boot-starter-vertx項目,提供一個GET /user?username=xxx查詢接口,使用Mybatis做持久化層實現DB查詢。麻雀雖小五臟俱全!

<https://github.com/wanghongfei/all-about-vertx-4

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章