Spring WebFlux框架的底层库Reactor详解 建议收藏

目录

前言

核心

流常用API

just

range

generate

create

delayElements

blockLast

concat、concatMap、concatWith

log

​编辑

fromIterable、fromArray、fromStream

buffer

limitRate

handle

publishOn

subscribeOn

transform

transformDeferred

defaultIfEmpty

switchIfEmpty

merge、mergeWith

mergeSequential

zip、zipWith

cache

block??

contextWrite

消费流

直接订阅使用

使用订阅者订阅

事件回调API

doOnSubscribe 流被订阅时的回调方法

doOnNext 每个元素到达时的回调方法

doOnEach 每个信号到达时的回调方法 

doOnRequest 流被请求时的回调方法

doOnComplete 流正常结束的回调方法

doOnTerminate 流被中断的回调方法 

doOnCancel 流被取消的回调方法

doOnError 流发生异常时的回调方法

 doOnDiscard 流中元素被忽略时的回调方法

自定义线程调度

Schedulers.immediate() 默认

Schedulers.single() 单开一个线程

Schedulers.boundedElastic() 线程池 

Schedulers.parallel() 使用ForkJoin

其他

错误处理

onErrorReturn

onErrorResume

onErrorMap

doOnError

doFinally

onErrorContinue

 onErrorComplete

onErrorStop

超时与重试

Sinks


前言

Reactor 是一个用于构建异步应用程序的库。它是Spring WebFlux框架的底层库之一,用于事件驱动的非阻塞IO。Reactor 是一个用于JVM的完全非阻塞的响应式编程框架。官网地址为https://projectreactor.io/

如果不了解响应式编程的可以移步这篇文章:java9的juc包中的Flow接口(响应式编程/发布订阅模式)

依赖可以在官网找到

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-bom</artifactId>
                <version>2023.0.8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
        <!--    reactor核心依赖和测试依赖    -->
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>

核心

Reactor中有两个核心API,Flux(用于多个元素)和Mono(用于0或1个元素)

        // 0个元素
        Mono<String> mono = Mono.empty();
        // 订阅流
        mono.subscribe(System.out::println);

        // 单个元素
        Mono<String> vhukze = Mono.just("vhukze");
        // 订阅流
        vhukze.subscribe(System.out::println);

        // 0个元素
        Flux<String> flux = Flux.empty();
        // 订阅流
        flux.subscribe(System.out::println);

        // 多个元素
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv");
        // 订阅流
        flux2.subscribe(System.out::println);

流常用API

跟stream流一样,流的方法是链式调用的,各方法之间也存在先后顺序

支持stream大部分API,比如map flatMap filter distinct count collect sort join reduce等

just

自定义元素

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv");
range
        // 此流中元素为0,1,2,3,4,5,6,7,8,9
        Flux<Integer> range = Flux.range(0, 10);

        // 此流中元素为10, 11, 12, 13
        Flux<Integer> range = Flux.range(10, 4);
generate

生成流(只能用于单线程,不适用多线程)

        // 生成1-20个数字的流,第一个参数指定初始值
        Flux.generate(() -> 1, (num, sink) -> {
            if (num <= 20) {
                sink.next(num);
            } else {
                sink.complete();
                // 也可以手动触发异常
//                sink.error(new RuntimeException("发生异常了"));
            }
            return num + 1;
        }).subscribe(System.out::println);

create

生成流(异步环境使用)

    static class MyBox {
        private FluxSink<Object> sink;

        public MyBox(FluxSink<Object> sink) {
            this.sink = sink;
        }

        public void in(Object name) {
            sink.next("元素" + name);
            System.out.println("进来了一个:" + name);
        }
    }
        Flux.create(sink -> {
            MyBox myBox = new MyBox(sink);

            for (int i = 0; i < 5; i++) {
                myBox.in(i);
            }
        }).subscribe(System.out::println);

 

delayElements

指定每个元素之间相隔时间

例如,每个元素之间相隔一秒

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .delayElements(Duration.ofSeconds(1))
blockLast

阻塞主线程直到最后一个元素完成

在main方法中测试订阅流的时候需要让主线程阻塞等待订阅者线程执行完成,可以用Thread.sleep或者System.in.read 手动控制主线程结束时间,而blockLast可以直接控制阻塞主线程直到最后一个元素完成

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .delayElements(Duration.ofSeconds(1));
        // 订阅流
        flux2.subscribe(it -> System.out.println("订阅" + it));

        flux2.blockLast();
concat、concatMap、concatWith

连接多个流

        Flux<String> flux2 = Flux.concat(Flux.just("vhukze", "lqyihv", "lqhlvu"), Flux.just("123", "321"));
        flux2.subscribe(it -> System.out.println("订阅" + it));

// 一个元素变多个,对元素类型不限制        
Flux.range(1, 3).concatMap(value -> Flux.just(value, "v")).log().subscribe();

// 连接的流元素类型要一致        
Flux.range(1, 3).concatWith(Flux.range(4, 3)).log().subscribe();

log

打印当前流的操作日志

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .log() // 在这里打日志输出的是Flux.just("vhukze", "lqyihv", "lqhlvu")流的信号日志
                .map(it -> it.concat("_haha"))
//                .log() // 在这里打日志输出的是.map(it -> it.concat("_haha"))流的信号日志
                .filter("vhukze_haha"::equals);

        flux2.subscribe(it -> System.out.println("订阅" + it));

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
//                .log() // 在这里打日志输出的是Flux.just("vhukze", "lqyihv", "lqhlvu")流的信号日志
                .map(it -> it.concat("_haha"))
                .log() // 在这里打日志输出的是.map(it -> it.concat("_haha"))流的信号日志
                .filter("vhukze_haha"::equals);

        flux2.subscribe(it -> System.out.println("订阅" + it));
fromIterable、fromArray、fromStream

把集合、数组、stream流转成flux流

随便演示一个

        List<String> list = new ArrayList<>();
        list.add("111");
        Flux.fromIterable(list).subscribe(System.out::println);

 

buffer

请求重塑 设置缓冲区大小(默认为1,即每次订阅者拿到一条数据)

之后订阅者拿到的是ArrayList(设置buffer(1)之后也会变成ArrayList)

        // 设置缓冲区大小为4
        Flux.range(1, 10).buffer(4).subscribe(System.out::println);

limitRate

限流

        // 限流 每次预取10个 到75%的数据时再请求后面的75%
        Flux.range(1, 100).log().limitRate(10).subscribe();

handle

自定义处理流

        Flux.range(1, 10).handle((value, sink) -> {
            System.out.println("当前值:" + value);
            sink.next(value + "加工了");
        }).subscribe(System.out::println);

publishOn

改变发布者的线程池 详见下方 自定义线程调度

subscribeOn

改变订阅者的线程池 详见下方 自定义线程调度

transform

无状态转换流,不会共享外部变量的值

其实就是transfrom中的代码只会执行一次

        AtomicInteger count = new AtomicInteger(1);

        Flux<String> flux = Flux.just("vhukze", "lqyihv").transform(values -> {
            // count++
            if (count.getAndIncrement() == 1) {
                return values.map(String::toUpperCase);
            }
            return values;
        });

        flux.subscribe(v -> System.out.println("v1 = " + v));
        flux.subscribe(v -> System.out.println("v2 = " + v));

可以看到两个订阅者都进到了if里面,并没有共享外部变量count的值 

transformDeferred

有状态转换,会共享外部变量的值

其实就是transfromDeferred中的代码会每个订阅者都执行一次

        AtomicInteger count = new AtomicInteger(1);

        Flux<String> flux = Flux.just("vhukze", "lqyihv").transformDeferred(values -> {
            // count++
            if (count.getAndIncrement() == 1) {
                return values.map(String::toUpperCase);
            }
            return values;
        });

        flux.subscribe(v -> System.out.println("v1 = " + v));
        flux.subscribe(v -> System.out.println("v2 = " + v));

同样的代码换成transformDeferred再看输出结果,第二个订阅者已经没有进入if了,因为count值已经变成了2

defaultIfEmpty
        // 如果流是空往流里面添加一个默认元素
        Flux.empty().defaultIfEmpty("vhukze").subscribe(v -> System.out.println("v = " + v));

 

switchIfEmpty
        // 如果流是空 转换成自定义的流
        Flux.empty().switchIfEmpty(Flux.just("vhukze", "lqhlvu")).subscribe(v -> System.out.println("v = " + v));
 
merge、mergeWith

按元素时间顺序合并多个流

mergeWith同理,只不过mergeWith是流的方法,merge是Flux的静态方法

        // 按每个元素的发布时间顺序合并多个流,下面第一个流一秒发布一个元素,第二个流两秒发布一个元素
        // 所以第一秒的元素是1 第二秒的元素是2和4 第三秒的元素是3 第四秒的元素是5 第五秒没有元素 第六秒的元素是6
        Flux.merge(Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(1)), Flux.just(4, 5, 6).delayElements(Duration.ofSeconds(2)))
                .subscribe(v -> System.out.println("v = " + v));

mergeSequential

按顺序合并流,与concat类似

  • Flux.concat:严格按顺序进行订阅,只有当前一个 Publisher 完全完成(即发出 onComplete 信号)时,才会订阅下一个 Publisher。因此,它保证了每个源 Publisher 完全按顺序执行。

  • Flux.mergeSequential:也会按顺序订阅 Publisher,但可以允许多个元素在合并时以更高的速率传递。当前一个 Publisher 完成时,马上订阅下一个 Publisher

zip、zipWith

多个流按个元素组成数组,多余的元素会被忽略,一次最多支持8个流。zipWith同理,只不过zipWith是流的方法,zip是Flux的静态方法

        Flux.zip(Flux.just(1, 2, 3), Flux.just(4, 5, 6), Flux.just(7, 8, 9, 10)).subscribe(v -> System.out.println("v = " + v));

 

cache

缓存指定数量的元素,后面订阅者加入之后可以拿到前面缓存的数据

下面代码是1-10的数字,每一秒发送一个,第二个订阅者五秒之后才订阅,不缓存的话只能拿到5和之后的数据,我这里指定了缓存两个元素

        Flux<Integer> cache = Flux.range(1, 10).delayElements(Duration.ofSeconds(1)).cache(2);

        // 第一个订阅者
        cache.subscribe();

        new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 第二个订阅者五秒之后才订阅
            cache.subscribe(v -> System.out.println("v = " + v));
        }).start();

block??

拿到流中的元素,block也是一种订阅者(阻塞式订阅)

        // 拿到流的最后一个元素
        Integer last = Flux.range(1, 10).blockLast();
        // 拿到流的第一个元素
        Integer first = Flux.range(1, 10).blockFirst();
        // 拿到所有元素 收集为一个Mono<List<T>>
        Mono<List<Integer>> listMono = Flux.range(1, 10).collectList();
        List<Integer> list = listMono.block();
contextWrite

数据共享

        Flux.range(1, 10)
                .transformDeferredContextual((flux, content) -> flux.map(v -> v * Integer.parseInt(content.get("num").toString())))
                // 共享数据,只对上游可见,对下游不可见。而且必须使用支持context的方法
                .contextWrite(Context.of("num", 10))
                .subscribe(v -> System.out.println("v = " + v));

消费流

直接订阅使用
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv");

        // 空订阅 不做任何事
        flux2.subscribe();

        // 订阅并指定一些操作
        flux2.subscribe(it -> {
            System.out.println(it);
            System.out.println("直接订阅使用了");
        });

        // 流异常时的操作 流在操作过程中发生异常 比如map中出现除零异常
        flux2.subscribe(it -> System.out.println("订阅" + it), throwable -> {
            System.out.println("流出现异常" + throwable);
        });

        // 正常结束时的操作
        flux2.subscribe(it -> System.out.println("订阅" + it), throwable -> {
            System.out.println("流出现异常" + throwable);
        }, () -> System.out.println("流正常结束了"));
使用订阅者订阅
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv");
        // 订阅流
        flux2.subscribe(new BaseSubscriber<>() {

            private Subscription subscription;

            @Override
            protected void hookOnSubscribe(Subscription subscription) {
                this.subscription = subscription;
                System.out.println("绑定了订阅关系");

                // 请求一个元素
                super.request(1);

                // 请求无限数据
//                super.requestUnbounded();
            }

            @Override
            protected void hookOnNext(String value) {
                System.out.println("元素到达:" + value);

                // 请求一个元素
                super.request(1);
            }

            @Override
            protected void hookOnComplete() {
                System.out.println("流正常完成");
            }

            @Override
            protected void hookOnError(Throwable throwable) {
                System.out.println("流发生异常");
            }

            @Override
            protected void hookOnCancel() {
                System.out.println("流被取消");
            }

            @Override
            protected void hookFinally(SignalType type) {
                // hookFinally作用跟try catch的finally块效果类似 无论成功失败最后执行
            }
        });

事件回调API

当流发生某些事时触发的方法,全部以doOn开头

首先要明白一点,流只有被消费才有用,被消费才会触发各种回调

因为所有方法都是链式调用的,存在顺序问题,比如开头就调用了doOnNext,此时获取到的元素为原始元素,如果调用了map对元素进行了操作,再在后面调用doOnNext,此时获取到的元素则是map之后的元素。同理 其他回调方法也是一样,不同位置的doOnError捕获的异常也是不同位置的

doOnSubscribe 流被订阅时的回调方法
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv")
                .doOnSubscribe(it -> System.out.println("流被订阅:" + it));
        // 订阅流
        flux2.subscribe(System.out::println);

doOnNext 每个元素到达时的回调方法
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv")
                .doOnNext(it -> System.out.println("元素到达:" + it));
        // 订阅流
        flux2.subscribe(System.out::println);

 

doOnEach 每个信号到达时的回调方法 

与doOnNext的区别就是,doOnEach获取的是每个信号,他的参数是一个对象,对象中包含元素值、信号类型、异常信息等属性

流的所有信号类型如下

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv")
                .doOnEach(stringSignal -> System.out.println("元素到达:" + stringSignal));
        // 订阅流
        flux2.subscribe(System.out::println);

doOnRequest 流被请求时的回调方法
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .doOnRequest(count -> System.out.println("流被请求" + count + "个元素"));
        // 订阅流
        flux2.subscribe(new BaseSubscriber<>() {

            @Override
            protected void hookOnSubscribe(Subscription subscription) {
                System.out.println("绑定了订阅关系");

                // 请求一个元素
                super.request(1);
            }

            @Override
            protected void hookOnNext(String value) {
                System.out.println("元素到达:" + value);

                super.request(1);
            }

            @Override
            protected void hookOnComplete() {
                System.out.println("流正常完成");
            }

            @Override
            protected void hookOnError(Throwable throwable) {
                System.out.println("(订阅者)流发生异常:" + throwable);
            }

            @Override
            protected void hookOnCancel() {
                System.out.println("流被取消");
            }

            @Override
            protected void hookFinally(SignalType type) {
                // hookFinally作用跟try catch的finally块效果类似 无论成功失败最后执行
                System.out.println("最终结束:" + type);
            }
        });

doOnComplete 流正常结束的回调方法
        // 多个元素
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv")
                // 流正常消费完成的回调方法
                .doOnComplete(() -> System.out.println("流正常结束了"));
        // 订阅流
        flux2.subscribe(System.out::println);

doOnTerminate 流被中断的回调方法 

在流发生异常或取消时触发

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .map(it -> {
                    Integer.parseInt(it);
                    return it;
                })
                .doOnTerminate(() -> System.out.println("流被中断"));

doOnCancel 流被取消的回调方法
        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .doOnCancel(() -> System.out.println("流被取消了"));
        // 订阅流
        flux2.subscribe(new BaseSubscriber<>() {

            @Override
            protected void hookOnSubscribe(Subscription subscription) {
                System.out.println("绑定了订阅关系");

                // 请求一个元素
                super.request(1);
            }

            @Override
            protected void hookOnNext(String value) {
                System.out.println("元素到达:" + value);

                if ("vhukze".equals(value)) {
                    super.request(1);
                } else {
                    // 取消
                    super.cancel();
                }
            }

            @Override
            protected void hookOnComplete() {
                System.out.println("流正常完成");
            }

            @Override
            protected void hookOnError(Throwable throwable) {
                System.out.println("流发生异常");
            }

            @Override
            protected void hookOnCancel() {
                System.out.println("流被取消");
            }

            @Override
            protected void hookFinally(SignalType type) {
                // hookFinally作用跟try catch的finally块效果类似 无论成功失败最后执行
            }
        });

doOnError 流发生异常时的回调方法

需要注意的是,如果在订阅者端发生的异常只会被当前订阅者捕获到,如下

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .doOnError(throwable -> System.out.println("流发生异常:" + throwable));
        // 订阅流
        flux2.subscribe(new BaseSubscriber<>() {

            @Override
            protected void hookOnSubscribe(Subscription subscription) {
                System.out.println("绑定了订阅关系");

                // 请求一个元素
                super.request(1);
            }

            @Override
            protected void hookOnNext(String value) {
                System.out.println("元素到达:" + value);

                if ("vhukze".equals(value)) {
                    super.request(1);
                } else {
                    // 抛异常
                    throw new RuntimeException("名称不对异常");
                }
            }

            @Override
            protected void hookOnComplete() {
                System.out.println("流正常完成");
            }

            @Override
            protected void hookOnError(Throwable throwable) {
                System.out.println("(订阅者)流发生异常:" + throwable);
            }

            @Override
            protected void hookOnCancel() {
                System.out.println("流被取消");
            }

            @Override
            protected void hookFinally(SignalType type) {
                // hookFinally作用跟try catch的finally块效果类似 无论成功失败最后执行
                System.out.println("最终结束:" + type);
            }
        });

在流处理时发生的异常才会被流的回调方法和订阅者感知到

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .map(it -> {
                    Integer.parseInt(it);
                    return it;
                })
                .doOnError(throwable -> System.out.println("流发生异常:" + throwable));
        // 订阅流
        flux2.subscribe(new BaseSubscriber<>() {

            @Override
            protected void hookOnSubscribe(Subscription subscription) {
                System.out.println("绑定了订阅关系");

                // 请求一个元素
                super.request(1);
            }

            @Override
            protected void hookOnNext(String value) {
                System.out.println("元素到达:" + value);

                if ("vhukze".equals(value)) {
                    super.request(1);
                } else {
                    // 抛异常
                    throw new RuntimeException("名称不对异常");
                }
            }

            @Override
            protected void hookOnComplete() {
                System.out.println("流正常完成");
            }

            @Override
            protected void hookOnError(Throwable throwable) {
                System.out.println("(订阅者)流发生异常:" + throwable);
            }

            @Override
            protected void hookOnCancel() {
                System.out.println("流被取消");
            }

            @Override
            protected void hookFinally(SignalType type) {
                // hookFinally作用跟try catch的finally块效果类似 无论成功失败最后执行
                System.out.println("最终结束:" + type);
            }
        });

 doOnDiscard 流中元素被忽略时的回调方法

在下面代码中我使用filter过滤掉了除vhukze外的其他字符串,可以看到订阅时只拿到了vhukze,而其他元素则在doOnDiscard 中出现了

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .filter("vhukze"::equals)
                .doOnDiscard(String.class, it -> System.out.println("丢弃" + it));
        // 订阅流
        flux2.subscribe(it -> System.out.println("订阅" + it));

自定义线程调度

生成流、操作流和发布流默认都是在当前线程操作的,只有订阅者的操作是异步的

下方用publishOn演示,subscribeOn同理

Schedulers.immediate() 默认
        // Schedulers.immediate() 使用当前线程(默认不设置publishOn时就是这个)
        Flux.range(1, 10).publishOn(Schedulers.immediate()).log().subscribe();

Schedulers.single() 单开一个线程
        // Schedulers.single() 单开一个线程
        Flux.range(1, 10).publishOn(Schedulers.single()).log().subscribe();

Schedulers.boundedElastic() 线程池 
        // Schedulers.boundedElastic() 使用一个有限大小、弹性的线程池 线程总数为10*CPU核心数 队列默认十万
        Flux.range(1, 10).publishOn(Schedulers.boundedElastic()).log().subscribe();

Schedulers.parallel() 使用ForkJoin
        Flux.range(1, 10).publishOn(Schedulers.parallel()).log().subscribe();

场景实践 100个数据分10个线程处理 (实际业务中可能10000万数据分批处理)

        Flux.range(1,100)
                .buffer(10)
                .parallel(10)
                .runOn(Schedulers.newParallel("newThread"))
                .log()
                .subscribe(v-> System.out.println("v = " + v));

其他

可以使用Schedulers.newBoundedElastic自己创建boundedElastic,自定义线程池的参数

也可以使用Schedulers.fromExecutor传入一个你自己已有的线程池

错误处理

onErrorReturn

拦截异常并返回一个静态值,订阅者不会感知到异常,流也会正常完成

        Flux.just(1, 2, 0).map(v -> 100 / v).onErrorReturn(444)
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

如果没有onErrorReturn的话,会打印发生的异常并且不会正常结束 

并且可以进行异常断言,我这里发生的是运算异常,如果我指定只拦截空指针异常,则这里不会被拦截,依然会被异常中断

        // ArithmeticException 数学运算异常
        Flux.just(1, 2, 0).map(v -> 100 / v).onErrorReturn(NullPointerException.class, 444)
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

onErrorResume

拦截异常并执行一个方法返回一个流,订阅者不会感知到异常,流也会正常完成

并且可以拿到异常值,可以根据具体异常对应处理,返回不同流

跟onErrorReturn一样也支持异常断言

        Flux.just(1, 2, 0).map(v -> 100 / v).onErrorResume(err -> Flux.just(444, 666))
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

onErrorResume还支持返回一个其他异常,比如把当前的异常拿到之后根据不同异常返回我们自己的业务异常,方便全局异常进行处理。 我这里用RuntimeException举例

        Flux.just(1, 2, 0).map(v -> 100 / v).onErrorResume(err -> Flux.error(new RuntimeException("发生了业务异常")))
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

onErrorMap

同样也是拿到当前异常返回自定义异常

        Flux.just(1, 2, 0).map(v -> 100 / v).onErrorMap(err -> new RuntimeException("发生了业务异常"))
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

doOnError

doOnError在上面的事件回调API中也提过了,是用来感知流异常的,不影响后续元素,并且不会拦截异常,订阅者也能感知到异常。详见上面的doOnError 流发生异常时的回调方法

doFinally

看名字也能猜到,不管流正常还是异常都能执行的方法,并且可以拿到流的信号

        Flux.just(1, 2, 0).map(v -> 100 / v).doFinally(signalType -> System.out.println("流信号为" + signalType))
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

onErrorContinue

发生异常后继续往后面执行

        Flux.just(1, 2, 0, 3).map(v -> 100 / v)
                .onErrorContinue((throwable, value) -> {
                    System.out.println("发生的异常" + throwable + ";异常的元素" + value + "; 继续执行后面的操作……");
                })
                .subscribe(v -> System.out.println("元素值 = " + v),
                        e -> System.out.println("发生异常 = " + e),
                        () -> System.out.println("正常结束"));

 onErrorComplete

流发生异常时把异常信号转为结束信号,结果就是流发生异常时订阅者会收到流正常结束的信号而不是流异常的信号

        Flux<String> flux2 = Flux.just("vhukze", "lqyihv", "lqhlvu")
                .map(it -> {
                    int a = 1 / 0;
                    return it;
                }).onErrorComplete();

        flux2.subscribe(it -> System.out.println("订阅" + it),
                throwable -> System.out.println("流出现异常" + throwable),
                () -> System.out.println("流正常结束了"));

onErrorStop

流发生错误后结束,所有订阅者都结束。跟onErrorComplete不同的是onErrorComplete是正常结束,onErrorStop是异常结束

超时与重试

使用timeout方法设置超时时间,使用retry方法指定重试次数(retry不传次数则无限重试)

下面代码中设置的每个元素延时三秒发送,超时时间为1秒,肯定会触发超时,又设置了重试两次,可以看到输出的日志中出现了三次超时错误

Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(3)).timeout(Duration.ofSeconds(1)).log().retry(2).subscribe();

Sinks

        Sinks.empty(); // 空管道
        Sinks.one(); // 单个元素的管道 相当于Mono
        Sinks.many(); // 多个元素的管道 相当于Flux

        Sinks.many().unicast(); // 单播 只允许一个订阅者订阅
        Sinks.many().multicast(); // 多播 允许多个订阅者订阅
        Sinks.many().replay().all(); // 可重放 为后来加入的订阅者从头开始推送数据
        Sinks.many().replay().limit(3); // 指定重放范围 为后来加入的订阅者从前三个元素开始推送
        Sinks.many().replay().latest(); // 重放最后一个元素

        // 重放相当于Flux中的cache

具体使用

        // 创建单播的背压队列
        Sinks.Many<String> many = Sinks.many().unicast().onBackpressureBuffer();

        // 往队列推送数据
        for (int i = 0; i < 5; i++) {
            many.tryEmitNext("i = " + i);
        }

        // 转成Flux并订阅
        many.asFlux().subscribe(System.out::println);

推荐阅读 做了个springboot接口参数解密的工具,我给它命名为万能钥匙(已上传maven中央仓库,附详细使用说明)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿演

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值