引子:被譽爲「中國大數據第一人」的塗子沛先生在其成名做《數據之巔》裏提到,摩爾定律、社交媒體、數據挖掘是大數據的三大成因。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】響應式Web框架實戰(上)github
上篇介紹瞭如何使用Spring MVC註解實現一個響應式Web應用(如下簡稱RP應用),本篇接着介紹另外一種實現方式——Router Functions。web
Router Functions是Spring 5新引入的一套Reactive風格(基於Flux和Mono)的函數式接口,主要包括RouterFunction
,HandlerFunction
和HandlerFilterFunction
,分別對應Spring MVC中的@RequestMapping
,@Controller
和HandlerInterceptor
(或者Servlet規範中的Filter
)。spring
和Router Functions搭配使用的是兩個新的請求/響應模型,ServerRequest
和ServerResponse
,這兩個模型一樣提供了Reactive風格的接口。mongodb
下面接着看我GitHub上的示例工程裏的例子。編程
@Configuration public class RestaurantServer implements CommandLineRunner { @Autowired private RestaurantHandler restaurantHandler; /** * 註冊自定義RouterFunction */ @Bean public RouterFunction<ServerResponse> restaurantRouter() { RouterFunction<ServerResponse> router = route(GET("/reactive/restaurants").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::findAll) .andRoute(GET("/reactive/delay/restaurants").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::findAllDelay) .andRoute(GET("/reactive/restaurants/{id}").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::get) .andRoute(POST("/reactive/restaurants").and(accept(APPLICATION_JSON_UTF8)).and(contentType(APPLICATION_JSON_UTF8)), restaurantHandler::create) .andRoute(DELETE("/reactive/restaurants/{id}").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::delete) // 註冊自定義HandlerFilterFunction .filter((request, next) -> { if (HttpMethod.PUT.equals(request.method())) { return ServerResponse.status(HttpStatus.BAD_REQUEST).build(); } return next.handle(request); }); return router; } @Override public void run(String... args) throws Exception { RouterFunction<ServerResponse> router = restaurantRouter(); // 轉化爲通用的Reactive HttpHandler HttpHandler httpHandler = toHttpHandler(router); // 適配成Netty Server所需的Handler ReactorHttpHandlerAdapter httpAdapter = new ReactorHttpHandlerAdapter(httpHandler); // 建立Netty Server HttpServer server = HttpServer.create("localhost", 9090); // 註冊Handler並啓動Netty Server server.newHandler(httpAdapter).block(); } }
能夠看到,使用Router Functions實現RP應用時,你須要本身建立和管理容器,也就是說Spring 5並無針對Router Functions提供IoC支持,這是Router Functions和Spring MVC相比最大的不一樣。除此以外,你須要經過RouterFunction
的API(而不是註解)來配置路由表和過濾器。對於簡單的應用,這樣作問題不大,但對於上規模的應用,就會致使兩個問題:1)Router的定義愈來愈龐大;2)因爲URI和Handler分開定義,路由表的維護成本愈來愈高。那爲何Spring 5會選擇這種方式定義Router呢?接着往下看。併發
@Component public class RestaurantHandler { /** * 擴展ReactiveCrudRepository接口,提供基本的CRUD操做 */ private final RestaurantRepository restaurantRepository; /** * spring-boot-starter-data-mongodb-reactive提供的通用模板 */ private final ReactiveMongoTemplate reactiveMongoTemplate; public RestaurantHandler(RestaurantRepository restaurantRepository, ReactiveMongoTemplate reactiveMongoTemplate) { this.restaurantRepository = restaurantRepository; this.reactiveMongoTemplate = reactiveMongoTemplate; } public Mono<ServerResponse> findAll(ServerRequest request) { Flux<Restaurant> result = restaurantRepository.findAll(); return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class); } public Mono<ServerResponse> findAllDelay(ServerRequest request) { Flux<Restaurant> result = restaurantRepository.findAll().delayElements(Duration.ofSeconds(1)); return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class); } public Mono<ServerResponse> get(ServerRequest request) { String id = request.pathVariable("id"); Mono<Restaurant> result = restaurantRepository.findById(id); return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class); } public Mono<ServerResponse> create(ServerRequest request) { Flux<Restaurant> restaurants = request.bodyToFlux(Restaurant.class); Flux<Restaurant> result = restaurants .buffer(10000) .flatMap(rs -> reactiveMongoTemplate.insert(rs, Restaurant.class)); return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class); } public Mono<ServerResponse> delete(ServerRequest request) { String id = request.pathVariable("id"); Mono<Void> result = restaurantRepository.deleteById(id); return ok().contentType(APPLICATION_JSON_UTF8).build(result); } }
對比上篇的RestaurantController
,因爲去除了路由信息,RestaurantHandler
變得很是函數化,能夠說就是一組相關的HandlerFunction
的集合,同時各個方法的可複用性也大爲提高。這就回答了上一小節提出的疑問,即以犧牲可維護性爲代價,換取更好的函數特性。
@RunWith(SpringRunner.class) @SpringBootTest public class RestaurantHandlerTests extends BaseUnitTests { @Autowired private RouterFunction<ServerResponse> restaurantRouter; @Override protected WebTestClient prepareClient() { WebTestClient webClient = WebTestClient.bindToRouterFunction(restaurantRouter) .configureClient().baseUrl("http://localhost:9090").responseTimeout(Duration.ofMinutes(1)).build(); return webClient; } }
和針對Controller的單元測試相比,編寫Handler的單元測試的主要區別在於初始化WebTestClient
方式的不一樣,測試方法的主體能夠徹底複用。
到此,有關響應式編程的介紹就暫且告一段落。回顧這四篇文章,我先是從響應式宣言提及,而後介紹了響應式編程的基本概念和關鍵特性,而且詳解了Spring 5中和響應式編程相關的新特性,最後以一個示例應用結尾。但願讀完這些文章,對你理解響應式編程能有所幫助。歡迎你到個人留言板分享,和你們一塊兒過過招。