【Spring 5】響應(yīng)式Web框架實(shí)戰(zhàn)(下)

引子:被譽(yù)為“中國(guó)大數(shù)據(jù)第一人”的涂子沛先生在其成名作《數(shù)據(jù)之巔》里提到蹬昌,摩爾定律混驰、社交媒體、數(shù)據(jù)挖掘是大數(shù)據(jù)的三大成因。IBM的研究稱栖榨,整個(gè)人類文明所獲得的全部數(shù)據(jù)中昆汹,有90%是過(guò)去兩年內(nèi)產(chǎn)生的。在此背景下婴栽,包括NoSQL满粗,Hadoop, Spark, Storm, Kylin在內(nèi)的大批新技術(shù)應(yīng)運(yùn)而生。其中以RxJavaReactor為代表的響應(yīng)式(Reactive)編程技術(shù)針對(duì)的就是經(jīng)典的大數(shù)據(jù)4V定義(Volume愚争,Variety映皆,Velocity,Value)中的Velocity轰枝,即高并發(fā)問(wèn)題捅彻,而在即將發(fā)布的Spring 5中,也引入了響應(yīng)式編程的支持狸膏。在接下來(lái)的幾周沟饥,我會(huì)圍繞響應(yīng)式編程分三期與你分享我的一些學(xué)習(xí)心得。本篇是第三篇(下)湾戳,通過(guò)一個(gè)簡(jiǎn)單的Spring 5示例應(yīng)用贤旷,探一探即將于下月底發(fā)布的Spring 5的究竟。

前情概要:

1 回顧

上篇介紹了如何使用Spring MVC注解實(shí)現(xiàn)一個(gè)響應(yīng)式Web應(yīng)用(以下簡(jiǎn)稱RP應(yīng)用)砾脑,本篇接著介紹另一種實(shí)現(xiàn)方式——Router Functions幼驶。

2 實(shí)戰(zhàn)

2.1 Router Functions

Router Functions是Spring 5新引入的一套R(shí)eactive風(fēng)格(基于Flux和Mono)的函數(shù)式接口,主要包括RouterFunction韧衣,HandlerFunctionHandlerFilterFunction盅藻,分別對(duì)應(yīng)Spring MVC中的@RequestMapping@ControllerHandlerInterceptor(或者Servlet規(guī)范中的Filter)畅铭。

和Router Functions搭配使用的是兩個(gè)新的請(qǐng)求/響應(yīng)模型氏淑,ServerRequestServerResponse,這兩個(gè)模型同樣提供了Reactive風(fēng)格的接口硕噩。

2.2 示例代碼

下面接著看我GitHub上的示例工程里的例子假残。

2.2.1 自定義RouterFunction和HandlerFilterFunction

@Configuration
public class RestaurantServer implements CommandLineRunner {

    @Autowired
    private RestaurantHandler restaurantHandler;

    /**
     * 注冊(cè)自定義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)
                // 注冊(cè)自定義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();
        // 轉(zhuǎn)化為通用的Reactive HttpHandler
        HttpHandler httpHandler = toHttpHandler(router);
        // 適配成Netty Server所需的Handler
        ReactorHttpHandlerAdapter httpAdapter = new ReactorHttpHandlerAdapter(httpHandler);
        // 創(chuàng)建Netty Server
        HttpServer server = HttpServer.create("localhost", 9090);
        // 注冊(cè)Handler并啟動(dòng)Netty Server
        server.newHandler(httpAdapter).block();
    }
}

可以看到,使用Router Functions實(shí)現(xiàn)RP應(yīng)用時(shí)炉擅,你需要自己創(chuàng)建和管理容器辉懒,也就是說(shuō)Spring 5并沒(méi)有針對(duì)Router Functions提供IoC支持,這是Router Functions和Spring MVC相比最大的不同谍失。除此之外眶俩,你需要通過(guò)RouterFunction的API(而不是注解)來(lái)配置路由表和過(guò)濾器。對(duì)于簡(jiǎn)單的應(yīng)用快鱼,這樣做問(wèn)題不大颠印,但對(duì)于上規(guī)模的應(yīng)用纲岭,就會(huì)導(dǎo)致兩個(gè)問(wèn)題:1)Router的定義越來(lái)越龐大;2)由于URI和Handler分開定義嗽仪,路由表的維護(hù)成本越來(lái)越高荒勇。那為什么Spring 5會(huì)選擇這種方式定義Router呢?接著往下看闻坚。

2.2.2 自定義HandlerFunction

@Component
public class RestaurantHandler {

    /**
     * 擴(kuò)展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);
    }
}

對(duì)比上篇RestaurantController,由于去除了路由信息窿凤,RestaurantHandler變得非常函數(shù)化仅偎,可以說(shuō)就是一組相關(guān)的HandlerFunction的集合,同時(shí)各個(gè)方法的可復(fù)用性也大為提升雳殊。這就回答了上一小節(jié)提出的疑問(wèn)橘沥,即以犧牲可維護(hù)性為代價(jià),換取更好的函數(shù)特性夯秃。

2.3 單元測(cè)試

@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;
    }
}

和針對(duì)Controller的單元測(cè)試相比座咆,編寫Handler的單元測(cè)試的主要區(qū)別在于初始化WebTestClient方式的不同,測(cè)試方法的主體可以完全復(fù)用仓洼。

3 小結(jié)

到此介陶,有關(guān)響應(yīng)式編程的介紹就暫且告一段落∩ǎ回顧這四篇文章哺呜,我先是從響應(yīng)式宣言說(shuō)起,然后介紹了響應(yīng)式編程的基本概念和關(guān)鍵特性箕戳,并且詳解了Spring 5中和響應(yīng)式編程相關(guān)的新特性某残,最后以一個(gè)示例應(yīng)用結(jié)尾。希望讀完這些文章陵吸,對(duì)你理解響應(yīng)式編程能有所幫助玻墅。歡迎你到我的留言板分享,和大家一起過(guò)過(guò)招壮虫。

4 參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椭豫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子旨指,更是在濱河造成了極大的恐慌,老刑警劉巖喳整,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谆构,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡框都,警方通過(guò)查閱死者的電腦和手機(jī)搬素,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人熬尺,你說(shuō)我怎么就攤上這事摸屠。” “怎么了粱哼?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵季二,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我揭措,道長(zhǎng)胯舷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任绊含,我火速辦了婚禮桑嘶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘躬充。我一直安慰自己逃顶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布充甚。 她就那樣靜靜地躺著以政,像睡著了一般。 火紅的嫁衣襯著肌膚如雪津坑。 梳的紋絲不亂的頭發(fā)上妙蔗,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音疆瑰,去河邊找鬼眉反。 笑死,一個(gè)胖子當(dāng)著我的面吹牛穆役,可吹牛的內(nèi)容都是我干的寸五。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耿币,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梳杏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起淹接,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤十性,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后塑悼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劲适,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年厢蒜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霞势。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烹植。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖愕贡,靈堂內(nèi)的尸體忽然破棺而出草雕,到底是詐尸還是另有隱情,我是刑警寧澤固以,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布墩虹,位于F島的核電站,受9級(jí)特大地震影響嘴纺,放射性物質(zhì)發(fā)生泄漏败晴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一栽渴、第九天 我趴在偏房一處隱蔽的房頂上張望尖坤。 院中可真熱鬧,春花似錦闲擦、人聲如沸慢味。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纯路。三九已至,卻和暖如春寞忿,著一層夾襖步出監(jiān)牢的瞬間驰唬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工腔彰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叫编,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓霹抛,卻偏偏與公主長(zhǎng)得像搓逾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杯拐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容