Java ProjectReactor框架之Flux篇

Spring5現處在第四個預發布版,正式版將要發布了,它帶來的一大特性就是響應式框架Spring WebFlux。默認使用ProjectReactor框架。因此。本文通過ProjectReactor中的Flux,來學習使用該框架,以及了解其傳遞的思想。

本文基于Reactor3.1 rc1

Reactor官方地址http://projectreactor.io/,官方文檔寫的十分詳細,如果您有不錯的英文能力,建議直接閱讀官方文檔。

Spring WebFlux 實踐

首先,為大家帶來一個使用了ProjectReactor的例子,該例子使用Spring Boot 2.0.0.BUILD-SNAPSHOT。因Spring Boot推薦默認配置(約定)優先,可以極大減少大量的重復的模版化代碼,簡化搭建過程。

Spring Boot 2.0.0穩定版還未出,不過也快了,目前處在第四個里程碑版本。

step1:搭建環境

spring boot部分工具如idea提供了可視化操作,選擇reactive-web模塊即可(你也可以多選一些你需要的模塊),如果沒有可視化的工具,也可訪問官網的開始頁面https://start.spring.io/,或者在pom中引入一下模塊(web開發主流仍是maven,所以未采用gradle)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>

step2:編寫處理類

編寫一個簡單的處理類,TestHandler

@Service
@NonNullApi
public class TestHandler {

    public Mono<ServerResponse> data(ServerRequest request){
        Mono<String> mono =  Mono.justOrEmpty(request.queryParam("data"))
                .defaultIfEmpty("this is null")
                .map(it -> it.concat("! from server webflux!"));
        return ServerResponse.ok().body(mono,String.class);
    }

}

step3:編寫路由

spring webflux也提供了函數試的路由配置,如下

@Configuration
public class RoutingConfiguration {

    //...

    @Bean
    public RouterFunction<ServerResponse> testRouterFunction(TestHandler handle) {
        return RouterFunctions.route(GET("/test").and(accept(APPLICATION_JSON)), handle::data);
    }

}

step4:測試,驗證

當瀏覽器輸入http://localhost:8080/test,得到結果:this is null! from server webflux!
當瀏覽器輸入http://localhost:8688/test?data=hi,得到結果:hi! from server webflux!

我的webflux項目地址:GitHub

深入學習

看過實踐后,你會發現有大量的使用Flux和Mono,它們是什么呢?

Flux<T> 繼承自 Publisher<T> ,用于代表擁有 0 到 n 元素的流,相對于 Mono<T> (其包含0-1個元素) 更加復雜。所以弄懂了Flux,其實也已經對Mono熟悉了。

靜態方法

Flux一般通過靜態方法構造,所以先看看它的靜態方法。

combineLatest

public static <T,V> Flux<V> combineLatest(Function<Object[],V> combinator, Publisher<? extends T>... sources)
構建一個Flux,混合由多個的發布者發布最新事件.

Type Parameters:
T - 表示發布者的事件類型
V - 被混合者混合后的類型
Parameters:
sources - 發布者,提供事件
combinator - 混合者,接受最新的事件,處理并傳遞給下游。
Returns: 一個以Flux為基礎的混合流
不同的參數方法很多,這里都只展示一個。

concat

public static <T> Flux<T> concat(Publisher<? extends T>... sources)
用于連接一個流。與combineLatest不同的是,concat都是在前一個流完成后在連接新的流。而combineLatest,則哪個事件最先到的,哪個先處理。

Type Parameters:
T - 事件的類型
Parameters:
sources - 一系列的發布者
Returns: 一個新的Flux連接了所有的發布者,并傳遞給下游

concatDelayError

擁有與concat類似的方法,不同的是,遇到錯誤不提前攔截,而是等到最后發布的事件處理完成后

create,push

public static <T> Flux<T> create(Consumer<? super FluxSink<T>> emitter)
通過FluxSink API,以同步或者異步方式創建Flux。
例如:

 Flux.<String>create(emitter -> {

     ActionListener al = e -> {
         emitter.next(textField.getText());
     };
     // without cleanup support:

     button.addActionListener(al);

     // with cleanup support:

     button.addActionListener(al);
     emitter.onDispose(() -> {
         button.removeListener(al);
     });
 });

這是非常有用的,如果一個流,需要動態添加或者移除其他的多個事件,通過異步的api。而且,你將不必擔心被取消和背壓。
create(Consumer<? super FluxSink<T>> emitter, FluxSink.OverflowStrategy backpressure) 設置背壓方式
push方法用處與使用方式與create幾乎一致,它們唯一的區別在于CreateMode類型 create為PUSH_PULL,而push為PUSH_ONLY,從文檔中也可以一個為多線程一個為單線程

backpressure(背壓)概念的理解

這里,我摘自一位大神的話,背壓是指在異步場景中,被觀察者發送事件速度遠快于觀察者的處理速度的情況下,一種告訴上游的被觀察者降低發送速度的策略。簡而言之,背壓是流速控制的一種策略。

更多的背壓到http://www.lxweimin.com/p/2c4799fa91a4這里不多做介紹了

defer

public static <T> Flux<T> defer(Supplier<? extends Publisher<T>> supplier)
這個方法提供了一種惰性策略,發布者不會一開始發布消息,直到訂閱者創建實例.


Type Parameters:
T - 發布者發布或訂閱者接受的類型
Parameters:
supplier - 一個發布者的供應者,當訂閱的時候回調
Returns: 一個惰性的Flux

empty

public static <T> Flux<T> empty()
創建一個不含任何事件的流.

error

public static <T> Flux<T> error(Throwable error)
返回一個帶著立即終止標識和錯誤信息的流

first

public static <I> Flux<I> first(Publisher<? extends I>... sources)
挑選出第一個發布者,由其提供事件。能有效避免多個源的沖突。

from

public static <T> Flux<T> from(Publisher<? extends T> source)
public static <T> Flux<T> fromIterable(Iterable<? extends T> it)
public static <T> Flux<T> fromStream(Stream<? extends T> s)
從一個發布者創建一個flux流

fromArray,fromIterable,fromStream

public static <T> Flux<T> fromArray(T[] array)
通過一個數組,或者一個可迭代的元素,或者一個流,創建flux流.

Type Parameters:
T - 數組的類型和Flux的類型
Parameters:
emmm.. - 數組,可迭代的元素,流
Returns: 新的flux流

generate

public static <T> Flux<T> generate(Consumer<SynchronousSink<T>> generator)
Programmatically create a Flux by generating signals one-by-one via a consumer callback.

Type Parameters:
T - the value type emitted
Parameters:
generator - Consume the SynchronousSink provided per-subscriber by Reactor to generate a single signal on each pass.
Returns: a Flux
沒看懂,好像是說,通過編程方式創建一個一對一的消費回調

interval

public static Flux<Long> interval(Duration period)
間隔一定的時間,發送事件。
Runs on the Schedulers.parallel() Scheduler.

just

public static <T> Flux<T> just(T... data)
創建一個包含一系列元素的flux流

merge

public static <I> Flux<I> merge(Publisher<? extends I>... sources)
混合多個流,和combineLatest類似,但它要求是同類型的流合并,combineLatest需要提供合并方式

never

public static <T> Flux<T> never()
Create a Flux that will never signal any data, error or completion signal.

Type Parameters:
T - the Subscriber type target
Returns:
a never completing Flux
看一看,不是很明白,該流的用處。

range

public static Flux<Integer> range(int start, int count)
提供從start,到start + count的所有整數的flux流

switchOnNext

public static <T> Flux<T> switchOnNext(Publisher<? extends Publisher<? extends T>> mergedPublishers)
從最新的發布者那里獲取事件,如果有新的發布者加入,則改用新的發布者。
當最后一個發布者完成所有發布事件,并且沒有發布者加入,則flux完成。

using

public static <T,D> Flux<T> using(Callable<? extends D> resourceSupplier, Function<? super D,? extends Publisher<? extends T>> sourceSupplier, Consumer<? super D> resourceCleanup)
Uses a resource, generated by a supplier for each individual Subscriber, while streaming the values from a Publisher derived from the same resource and makes sure the resource is released if the sequence terminates or the Subscriber cancels.
Eager resource cleanup happens just before the source termination and exceptions raised by the cleanup Consumer may override the terminal even.

zip

public static <I,O> Flux<O> zip(Function<? super Object[],? extends O> combinator, Publisher<? extends I>... sources)
通過混合者,合并多個流成一個輸出流,一一對應合并


...

看一下下面的api
public static <T1,T2,T3,V> Flux<V> combineLatest(Publisher<? extends T1> source1, Publisher<? extends T2> source2, Publisher<? extends T3> source3, Function<Object[],V> combinator)
public static <T1,T2,T3,T4,V> Flux<V> combineLatest(Publisher<? extends T1> source1, Publisher<? extends T2> source2, Publisher<? extends T3> source3, Publisher<? extends T4> source4, Function<Object[],V> combinator)
...
public static <T1,T2> Flux<Tuple2<T1,T2>> zip(Publisher<? extends T1> source1, lisher<? extends T2> source2)
public static <T1,T2,T3> Flux<Tuple3<T1,T2,T3>> zip(Publisher<? extends T1> source1, lisher<? extends T2> source2, lisher<? extends T3> source3)
...
ヽ(o_ _)o摔倒,我也是服了project reactor 官方。

常用的實例方法

靜態的方法介紹完了,但是實例方法比靜態方法多太多,所以這里只舉常用的幾種介紹

all,any,hasElement,hasElements

這幾個方法調用,均返回包涵一個Boolean信號的Mono。

  • all(Predicate<? super T> predicate)表示所有值均滿足條件
  • any(Predicate<? super T> predicate)表示存在一個值滿足條件
  • hasElement(T t)表示是否存在該值
  • hasElements()表示是否擁有一個或多個元素

as,compose

public final <P> P as(Function<? super Flux<T>,P> transformer)
轉化flux為一個目標類型。
官方例子:flux.as(Mono::from).subscribe()
將flux通過Mono.from函數轉化為mono
public final <V> Flux<V> compose(Function<? super Flux<T>,? extends Publisher<V>> transformer)
compose與as的區別是轉化類型做了限制,必須繼承Publisher,同時compose是惰性的。在很多時候,寫法上沒有差別如flux.compose(Mono::from).subscribe()

blockFirst,blockLast

阻塞至第一個或者最后一個值處理完成

butter系列

該系列實例方法很多,作用是將一系列元素,分成一組或者多組,該方法可用在按組批量操作上,例如,以時間間隔分組,批量添加數據。

cache

如其名緩存,相當于復制一份用于接下來的操作,而當前的流將會被緩存起來,用于之后的操作。

cancelOn

public final Flux<T> cancelOn(Scheduler scheduler)
取消

cast

public final <E> Flux<E> cast(Class<E> clazz)
強轉

checkpoint

用于檢測當前節點,流中是否存在錯誤

collect系列

該系列實例方法,用于收集所有的元素到特定類型,如list、map等
處理完成時返回Mono

concatMap系列,flatMap系列

舉例說明吧,[[1,2],[4,5],[6,7,8]] -> [1,2,4,5,6,7,8]起這種轉化作用


flatMap系列一樣

concatWith

與concatMap不同,這是相加

defaultIfEmpty

public final Flux<T> defaultIfEmpty(T defaultV) 默認值

distinct

public final Flux<T> distinct()
去重,相對與jdk8,多了下面兩種方法
public final <V> Flux<T> distinct(Function<? super T,? extends V> keySelector)
public final <V,C extends Collection<? super V>> Flux<T> distinct(Function<? super T,? extends V> Supplier<C> distinctCollectionSupplier)
去除與V匹配的和第二個不怎么理解,,,這讓我想到了filter

do系列

還系列有doOnNext,doOnError,doOnCancel等等,均表示完成后觸發

elementAt

返回某一位置的值,類型為Mono<T>,可以設置默認值

filter

public final Flux<T> filter(Predicate<? super T> p)
過濾出滿足條件的

groupBy

public final <K> Flux<GroupedFlux<K,T>> groupBy(Function<? super T,? extends K> keyMapper)


分組,根據提供的keyMapper

My Blog: https://www.dnocm.com/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 一、基本數據類型 注釋 單行注釋:// 區域注釋:/* */ 文檔注釋:/** */ 數值 對于byte類型而言...
    龍貓小爺閱讀 4,288評論 0 16
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • 通用文件夾使用要求 Editor文件夾 由于unity是分別編譯的,會生成不同的dll文件,比如: Assembl...
    霸俊流年閱讀 1,232評論 0 2
  • 中國是少數幾個女性自殺率高于男性自殺率的國家之一
    柳卿卯木閱讀 218評論 0 0