引子:被譽為“中國大資料第一人”的塗子沛先生在其成名作《資料之巔》裡提到,摩爾定律、社交媒體、資料探勘是大資料的三大成因。IBM的研究稱,整個人類文明所獲得的全部資料中,有90%是過去兩年內產生的。在此背景下,包括NoSQL,Hadoop, Spark, Storm, Kylin在內的大批新技術應運而生。其中以RxJava和Reactor為代表的響應式(Reactive)程式設計技術針對的就是經典的大資料4V定義(Volume,Variety,Velocity,Value)中的Velocity,即高併發問題,而在即將釋出的Spring 5中,也引入了響應式程式設計的支援。在接下來的幾周,我會圍繞響應式程式設計分三期與你分享我的一些學習心得。本篇是第三篇,通過一個簡單的Spring 5示例應用,探一探即將於下月底釋出的Spring 5的究竟。
前情概要:
1 回顧
通過前兩篇的介紹,相信你對響應式程式設計和Spring 5已經有了一個初步的瞭解。下面我將以一個簡單的Spring 5應用為例,介紹如何使用Spring 5快速搭建一個響應式Web應用(以下簡稱RP應用)。
2 實戰
2.1 環境準備
首先,從GitHub下載我的這個示例應用,地址是github.com/emac/spring…。
然後,從MongoDB官網下載最新版本的MongoDB,然後在命令列下執行mongod &
啟動服務。
現在,可以先試著跑一下專案中自帶的測試用例。
./gradlew clean build
2.2 依賴介紹
接下來,看一下這個示例應用裡的和響應式程式設計相關的依賴。
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層,所有資料都不落地,經由各種Flux
和Mono
鋪設的“管道”,直供呼叫端。就像農夫山泉那句著名的廣告詞,我們不生產水,我們只是大自然的搬運工。
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。歡迎你到我的留言板分享,和大家一起過過招。