引子:被譽爲「中國大數據第一人」的塗子沛先生在其成名做《數據之巔》裏提到,摩爾定律、社交媒體、數據挖掘是大數據的三大成因。IBM的研究稱,整我的類文明所得到的所有數據中,有90%是過去兩年內產生的。在此背景下,包括NoSQL,Hadoop, Spark, Storm, Kylin在內的大批新技術應運而生。其中以RxJava和Reactor爲表明的響應式(Reactive)編程技術針對的就是經典的大數據4V定義(Volume,Variety,Velocity,Value)中的Velocity,即高併發問題,而在即將發佈的Spring 5中,也引入了響應式編程的支持。在接下來的幾周,我會圍繞響應式編程分三期與你分享個人一些學習心得。本篇是第三篇,經過一個簡單的Spring 5示例應用,探一探即將於下月底發佈的Spring 5的究竟。html
前情概要:java
【Spring 5】響應式Web框架前瞻react
響應式編程總覽git
經過前兩篇的介紹,相信你對響應式編程和Spring 5已經有了一個初步的瞭解。下面我將以一個簡單的Spring 5應用爲例,介紹如何使用Spring 5快速搭建一個響應式Web應用(如下簡稱RP應用)。github
首先,從GitHub下載個人這個示例應用,地址是https://github.com/emac/spring5-features-demo。web
而後,從MongoDB官網下載最新版本的MongoDB,而後在命令行下運行mongod &
啓動服務。spring
如今,能夠先試着跑一下項目中自帶的測試用例。mongodb
./gradlew clean build
數據庫
接下來,看一下這個示例應用裏的和響應式編程相關的依賴。編程
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框架)提供的官方測試工具庫。
不知道你是否還記得,在本系列第一篇【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層,全部數據都不落地,經由各類Flux
和Mono
鋪設的「管道」,直供調用端。就像農夫山泉那句著名的廣告詞,咱們不生產水,咱們只是大天然的搬運工。
和非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應用的單元測試,一樣也是數據不落地的流式風格。
在示例應用中能夠找到更多的單元測試。
以上就是Spring 5裏第一種,相信也將會是最經常使用的編寫RP應用的實現方式。介於篇幅緣由,這篇就先到這裏。下篇我將詳細介紹第二種方式,Router Functions。歡迎你到個人留言板分享,和你們一塊兒過過招。