【Spring 5】響應式Web框架實戰(上)

引子:被譽爲「中國大數據第一人」的塗子沛先生在其成名做《數據之巔》裏提到,摩爾定律、社交媒體、數據挖掘是大數據的三大成因。IBM的研究稱,整我的類文明所得到的所有數據中,有90%是過去兩年內產生的。在此背景下,包括NoSQL,Hadoop, Spark, Storm, Kylin在內的大批新技術應運而生。其中以RxJavaReactor爲表明的響應式(Reactive)編程技術針對的就是經典的大數據4V定義(Volume,Variety,Velocity,Value)中的Velocity,即高併發問題,而在即將發佈的Spring 5中,也引入了響應式編程的支持。在接下來的幾周,我會圍繞響應式編程分三期與你分享個人一些學習心得。本篇是第三篇,經過一個簡單的Spring 5示例應用,探一探即將於下月底發佈的Spring 5的究竟。html

前情概要:java

1 回顧

經過前兩篇的介紹,相信你對響應式編程和Spring 5已經有了一個初步的瞭解。下面我將以一個簡單的Spring 5應用爲例,介紹如何使用Spring 5快速搭建一個響應式Web應用(如下簡稱RP應用)。react

2 實戰

2.1 環境準備

首先,從GitHub下載個人這個示例應用,地址是github.com/emac/spring…git

而後,從MongoDB官網下載最新版本的MongoDB,而後在命令行下運行mongod &啓動服務。github

如今,能夠先試着跑一下項目中自帶的測試用例。web

./gradlew clean buildspring

2.2 依賴介紹

接下來,看一下這個示例應用裏的和響應式編程相關的依賴。mongodb

compile('org.springframework.boot:spring-boot-starter-webflux')
compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
testCompile('io.projectreactor.addons:reactor-test')複製代碼
  • spring-boot-starter-webflux: 啓用Spring 5的RP(Reactive Programming)支持,這是使用Spring 5開發RP應用的必要條件,就比如spring-boot-starter-web之於傳統的Spring MVC應用。
  • spring-boot-starter-data-mongodb-reactive: Spring 5中新引入的針對MongoDB的Reactive Data擴展庫,容許經過統一的RP風格的API操做MongoDB。
  • io.projectreactor.addons:reactor-test: Reactor(Spring 5默認使用的RP框架)提供的官方測試工具庫。

2.3 示例代碼

不知道你是否還記得,在本系列第一篇【Spring 5】響應式Web框架前瞻裏提到,Spring 5提供了Spring MVC註解和Router Functions兩種方式來編寫RP應用。本篇我就先用你們最熟悉的MVC註解來展現如何編寫一個最簡單的RP Controller。數據庫

@RestController
public class RestaurantController {

    /** * 擴展ReactiveCrudRepository接口,提供基本的CRUD操做 */
    private final RestaurantRepository restaurantRepository;

    /** * spring-boot-starter-data-mongodb-reactive提供的通用模板 */
    private final ReactiveMongoTemplate reactiveMongoTemplate;

    public RestaurantController(RestaurantRepository restaurantRepository, ReactiveMongoTemplate reactiveMongoTemplate) {
        this.restaurantRepository = restaurantRepository;
        this.reactiveMongoTemplate = reactiveMongoTemplate;
    }

    @GetMapping("/reactive/restaurants")
    public Flux<Restaurant> findAll() {
        return restaurantRepository.findAll();
    }

    @GetMapping("/reactive/restaurants/{id}")
    public Mono<Restaurant> get(@PathVariable String id) {
        return restaurantRepository.findById(id);
    }

    @PostMapping("/reactive/restaurants")
    public Flux<Restaurant> create(@RequestBody Flux<Restaurant> restaurants) {
        return restaurants
                .buffer(10000)
                .flatMap(rs -> reactiveMongoTemplate.insert(rs, Restaurant.class));
    }

    @DeleteMapping("/reactive/restaurants/{id}")
    public Mono<Void> delete(@PathVariable String id) {
        return restaurantRepository.deleteById(id);
    }
}複製代碼

能夠看到,實現一個RP Controller和一個普通的Controller是很是相似的,最核心的區別是,優先使用RP中最基礎的兩種數據類型,Flux(對應多值)和Mono(單值),尤爲是方法的參數和返回值。即使是空返回值,也應封裝爲Mono<Void>。這樣作的目的是,使得應用可以以一種統一的符合RP規範的方式處理數據,最理想的狀況是從最底層的數據庫(或者其餘系統外部調用),到最上層的Controller層,全部數據都不落地,經由各類FluxMono鋪設的「管道」,直供調用端。就像農夫山泉那句著名的廣告詞,咱們不生產水,咱們只是大天然的搬運工。編程

2.4 單元測試

和非RP應用的單元測試相比,RP應用的單元測試主要是使用了一個Spring 5新引入的測試工具類,WebTestClient,專門用於測試RP應用。

@RunWith(SpringRunner.class)
@SpringBootTest
public class RestaurantControllerTests {

    @Test
    public void testNormal() throws InterruptedException {
        // start from scratch
        restaurantRepository.deleteAll().block();

        // prepare
        WebTestClient webClient = WebTestClient.bindToController(new RestaurantController(restaurantRepository, reactiveMongoTemplate)).build();
        Restaurant[] restaurants = IntStream.range(0, 100)
                .mapToObj(String::valueOf)
                .map(s -> new Restaurant(s, s, s))
                .toArray(Restaurant[]::new);

        // create
        webClient.post().uri("/reactive/restaurants")
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .syncBody(restaurants)
                .exchange()
                .expectStatus().isOk()
                .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                .expectBodyList(Restaurant.class)
                .hasSize(100)
                .consumeWith(rs -> Flux.fromIterable(rs)
                        .log()
                        .subscribe(r1 -> {
                            // get
                            webClient.get()
                                    .uri("/reactive/restaurants/{id}", r1.getId())
                                    .accept(MediaType.APPLICATION_JSON_UTF8)
                                    .exchange()
                                    .expectStatus().isOk()
                                    .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
                                    .expectBody(Restaurant.class)
                                    .consumeWith(r2 -> Assert.assertEquals(r1, r2));
                        })
                );
    }
}複製代碼

建立WebTestClient實例時,首先要綁定一下待測試的RP Controller。能夠看到,和業務類同樣,編寫RP應用的單元測試,一樣也是數據不落地的流式風格。

在示例應用中能夠找到更多的單元測試。

3 小結

以上就是Spring 5裏第一種,相信也將會是最經常使用的編寫RP應用的實現方式。介於篇幅緣由,這篇就先到這裏。下篇我將詳細介紹第二種方式,Router Functions。歡迎你到個人留言板分享,和你們一塊兒過過招。

4 參考

相關文章
相關標籤/搜索