webflux小结

本文介绍了Reactor中的Flux和Mono,它们作为响应式流的发布者,能处理元素值、错误信号和完成信号。文章详细阐述了两者之间的转换、常用操作符如map、flatMap、filter、retry等的用法,以及错误处理和背压策略,如onBackPressureBuffer和limitRate等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Flux与Mono

Reactor中的发布者(Publisher)由Flux和Mono两个类定义,它们都提供了丰富的操作符(operator)。
一个Flux对象代表一个包含0…N个元素的响应式序列,而一个Mono对象代表一个包含零/一个(0…1)元素的结果。

既然是“数据流”的发布者,Flux和Mono都可以发出三种“数据信号”:元素值、错误信号、完成信号,错误信号和完成信号都是终止信号,完成信号用于告知下游订阅者该数据流正常结束,错误信号终止数据流的同时将错误传递给下游订阅者。

这三种信号都不是一定要具备的:

  • 首先,错误信号和完成信号都是终止信号,二者不可能同时共存;
  • 如果没有发出任何一个元素值,而是直接发出完成/错误信号,表示这是一个空数据流;
  • 如果没有错误信号和完成信号,那么就是一个无限数据流。

操作符(Operator)

Flux和Mono提供了多种创建数据流的方法,just就是一种比较直接的声明数据流的方式,其参数就是数据元素。

Flux.just(1, 2, 3, 4, 5);
Mono.just(1);

Flux,还可以通过如下方式声明(分别基于数组、集合和Stream生成):

Integer[] array = new Integer[]{1,2,3,4,5};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);

Mono和flux互转:
Flux -> Mono<List>
1.第一种:如果 flux 是空的, single 会返回一个 Mono.error, 消费的时候就会抛异常

Flux<String> flux;
Mono<List<String>> mono = flux.buffer().single()

2.第二种:collectList 是把数据存在内存里,注意数据量对内存的影响

Flux<String> flux;
Mono<List<String>> mono = flux.collectList();

Mono -> Flux

List<A> list;
Flux<A> flux = Flux.fromIterable(list).collectList();

Flux<String> newFlux =listMono.flatMapMany(Flux::fromIterable);

map 和flatMap区别

  • map操作可以将数据元素进行转换/映射,得到一个新元素。
  • flatMap操作可以将每个数据元素转换/映射为一个流,然后将这些流合并为一个大的数据流。
    map和flatMap都可以对RxJava传入的数据进行变换。

举例略

注意:flatMap

流的合并是异步的,先来先到,并非是严格按照原始序列的顺序。想要按顺序获取数据可以用concatMap,concatMap与flatMap使用基本一致,它可以保证数据有序

filter
filter操作可以对数据元素进行筛选。

StepVerifier.create(Flux.range(1, 6)
            .filter(i -> i % 2 == 1)    // 1
            .map(i -> i * i))
            .expectNext(1, 9, 25)   // 2
            .verifyComplete();

doFinally
doFinally在序列终止(无论是 onComplete、onError还是取消)的时候被执行, 并且能够判断是什么类型的终止事件(完成、错误还是取消),以便进行针对性的处理。如:

LongAdder statsCancel = new LongAdder();    // 用LongAdder进行统计;

Flux<String> flux =
Flux.just("foo", "bar")
    .doFinally(type -> {
        if (type == SignalType.CANCEL)  // doFinally用SignalType检查了终止信号的类型;
          statsCancel.increment();  // 如果是取消,那么统计数据自增
    })
    .take(1);   // take(1)能够在发出1个元素后取消流。

retry
retry,用它可以对出现错误的序列进行重试。
请注意:retry对于上游Flux是采取的重订阅(re-subscribing)的方式,因此重试之后实际上已经一个不同的序列了, 发出错误信号的序列仍然是终止了的。举例如下:

Flux.range(1, 6)
    .map(i -> 10 / (3 - i))
    .retry(1)
    .subscribe(System.out::println, System.err::println);
Thread.sleep(100);  // 确保序列执行完

输出:

5
10
5
10
java.lang.ArithmeticException: / by zero

zip - 一对一合并
Flux的zip方法接受Flux或Mono为参数,Mono的zip方法只能接受Mono类型的参数。
它对两个Flux/Mono流每次各取一个元素,合并为一个二元组(Tuple2)
zip是静态方法,示例:

private Flux<String> getZipDescFlux() {
    String desc = "Zip two sources together, that is to say wait for all the sources to emit one element and combine these elements once into a Tuple2.";
    return Flux.fromArray(desc.split("\\s+"));  // 将英文说明用空格拆分为字符串流;
}

@Test
public void testSimpleOperators() throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(1);  // 定义一个CountDownLatch,初始为1,则会等待执行1次countDown方法后结束,不使用它的话,测试方法所在的线程会直接返回而不会等待数据流发出完毕;
    Flux.zip(
            getZipDescFlux(),
            Flux.interval(Duration.ofMillis(200)))  // 使用Flux.interval声明一个每200ms发出一个元素的long数据流;因为zip操作是一对一的,故而将其与字符串流zip之后,字符串流也将具有同样的速度;
            .subscribe(t -> System.out.println(t.getT1()), null, countDownLatch::countDown);    // zip之后的流中元素类型为Tuple2,使用getT1方法拿到字符串流的元素;定义完成信号的处理为countDown;
    countDownLatch.await(10, TimeUnit.SECONDS);     // countDownLatch.await(10, TimeUnit.SECONDS)会等待countDown倒数至0,最多等待10秒钟。
}

zip静态方法之外,还有zipWith等非静态方法,效果与之类似:

getZipDescFlux().zipWith(Flux.interval(Duration.ofMillis(200)))

数据流转换方法

concat
先运行完flux1之后再运行flux2

Flux.concat(hotFlux1(), hotFlux2())
                .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

merge
是按照时间先后执行

Flux.merge(hotFlux1(), hotFlux2())
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

对比concat和merge:
concat是合并的flux,按照顺序分别运行,flux1运行完成以后再运行flux2
merge是同时运行,根据时间先后运行

补充:
concatWith用法和concat基本相同,写法略有不同:

flux1().concatWith(flux2())
            .log()
            .subscribe();

mergeWith用法和merge相同,写法不同

hotFlux1().mergeWith(hotFlux2())
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

mergeSequential和concat有些相似,得到的结果类似
跟concat不同在于,订阅的源是hot型,接收数据后根据订阅顺序重新排序

Flux.mergeSequential(hotFlux1(), hotFlux2())
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

mergeOrdered用法
合并接收之后再排序

Flux.mergeOrdered(Comparator.reverseOrder(), hotFlux1(), hotFlux2())
                .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

combineLatest用法
跟concat和merge不同该方法是将多个源的最后得到元素通过函数进行融合的到新的值

Flux.combineLatest(hotFlux1(), hotFlux2(), (v1, v2) -> v1 + ":" + v2)
            .subscribe(i -> System.out.print("->"+i));
Thread.sleep(200);

Error处理

Reactor提供了其他的用于在链中处理错误的操作符(error-handling operators),使得对于错误信号的处理更加及时,处理方式更加多样化。

借助命令式编程风格的 try 代码块来作比较。我们都很熟悉在 try-catch 代码块中处理异常的几种方法。常见的包括如下几种:

  • 捕获并返回一个静态的缺省值。
  • 捕获并执行一个异常处理方法或动态计算一个候补值来顶替。
  • 捕获,并再包装为某一个业务相关的异常,然后再抛出业务异常。
  • 捕获,记录错误日志,然后继续抛出。 使用 finally 来清理资源,或使用 Java 7 引入的"try-with-resource"。

以上所有这些在 Reactor 都有相应的基于 error-handling 操作符处理方式。

  1. 捕获并返回一个静态的缺省值

onErrorReturn方法能够在收到错误信号的时候提供一个缺省值:

Flux.range(1, 6)
    .map(i -> 10/(i-3))
    .onErrorReturn(0)   // 当发生异常时提供一个缺省值0
    .map(i -> i*i)
    .subscribe(System.out::println, System.err::println);
  1. 捕获并执行一个异常处理方法或计算一个候补值来顶替
    onErrorResume方法能够在收到错误信号的时候提供一个新的数据流:
Flux.just(endpoint1, endpoint2)
    .flatMap(k -> callExternalService(k))   // 调用外部服务;
    .onErrorResume(e -> getFromCache(k));   // 如果外部服务异常,则从缓存中取值代替。提供新的数据流
  1. 捕获,并再包装为某一个业务相关的异常,然后再抛出业务异常
    有时候,我们收到异常后并不想立即处理,而是会包装成一个业务相关的异常交给后续的逻辑处理,可以使用onErrorMap方法:
Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))   // 调用外部服务;
    .onErrorMap(original -> new BusinessException("error", original)); // 如果外部服务异常,将其包装为业务相关的异常后再次抛出。

这一功能其实也可以用onErrorResume实现

Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))
    .onErrorResume(original -> Flux.error(
        new BusinessException("error", original)
    );
  1. 捕获,记录错误日志,然后继续抛出
    如果对于错误你只是想在不改变它的情况下做出响应(如记录日志),并让错误继续传递下去, 那么可以用doOnError 方法。如doOnXxx是只读的,对数据流不会造成影响:
Flux.just(endpoint1, endpoint2)
    .flatMap(k -> callExternalService(k)) 
    .doOnError(e -> {   // 只读地拿到错误信息,错误信号会继续向下游传递;
        log("error log : " + k);    // 记录日志。
    })
    .onErrorResume(e -> getFromCache(k)); 
  1. 使用 finally 来清理资源,或使用 Java 7 引入的 “try-with-resource”
Flux.using(
        () -> getResource(),    // 第一个参数获取资源;
        resource -> Flux.just(resource.getAll()),   // 第二个参数利用资源生成数据流;
        MyResource::clean   // 第三个参数最终清理资源。
);

背压策略
reactor提供了集中背压策略
onBackPressureBuffer - 顾名思义,来不及消费的数据先缓存在队列里(默认策略)
onBackPressureDrop - drop,丢掉,来不及消费的数据直接扔掉
onBackPressureLatest - 保留最新数据,一旦下游请求出现,立即推向下游
onBackPressureError - 下游消费速度跟不上,直接抛异常报错
limitRate(n) - 限速,上游一次最多发n个
举例推荐看:https://blog.youkuaiyun.com/FightingITPanda/article/details/119209864

官方文档
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html#zipWith-org.reactivestreams.Publisher

参考文章:
https://blog.youkuaiyun.com/wangkai525and526/article/details/107729472
https://www.jianshu.com/p/fcb4f4aebf68
https://cloud.tencent.com/developer/article/1526029

### WebFlux 框架的使用指南 Spring WebFlux 是 Spring Framework 5 中引入的一种响应式编程框架,旨在处理高并发、高性能和实时数据流应用。与传统的基于线程阻塞的 Spring MVC 不同,WebFlux 采用了非阻塞、事件驱动的编程模型,能够更加高效地利用系统资源,提升应用的性能和可伸缩性。 #### 环境搭建与依赖配置 要开始使用 Spring WebFlux,首先需要配置项目的依赖。对于 Maven 项目,可以在 `pom.xml` 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> ``` 如果你使用的是 Spring Boot,可以直接通过 Spring Initializr 创建一个包含 WebFlux 的项目。选择 "Web" -> "Spring WebFlux" 作为依赖项即可[^1]。 #### 基本概念 - **反应式流(Reactive Streams)**:WebFlux 使用反应式流来处理数据流,允许开发者编写非阻塞代码。 - **非阻塞 I/O**:WebFlux 利用 Netty 或 Undertow 等非阻塞 I/O 服务器来处理请求,从而提高应用的性能。 #### 构建简单的 WebFlux 应用 1. **创建项目**:使用 Spring Boot Initializr 创建一个新的 Spring Boot 项目,并选择 WebFlux 作为依赖。 2. **定义数据模型**:创建一个简单的 POJO 类来表示你的数据模型。 3. **编写控制器**:使用 `@RestController` 注解来定义 RESTful 控制器,并使用 `Mono` 或 `Flux` 来处理异步数据流。 4. **启动应用并测试**:运行应用并通过浏览器或 Postman 测试 API 端点。 ### WebFlux 的特性 - **响应式数据库操作**:WebFlux 支持与响应式数据库(如 MongoDB 和 Cassandra)进行交互,使用 Spring Data 的响应式库可以轻松实现这一点。 - **异常处理**:WebFlux 提供了多种方式来处理异常,包括使用 `@ExceptionHandler` 注解和 `ControllerAdvice` 类。 - **实时数据流示例**:WebFlux 可以处理实时数据流,例如使用 WebSocket 进行双向通信。 - **WebFlux 与 Spring Security 集成**:WebFlux 可以与 Spring Security 无缝集成,提供安全的响应式应用。 ### WebFlux 的文档信息 Spring Framework 的官方文档提供了关于如何使用 Spring WebFlux 的详细信息和指南。这份文档包含了从入门到高级主题的各种内容,涵盖了 Spring WebFlux 的所有主要组件。你可以访问 [Spring Framework Documentation](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/) 获取更多详细信息[^4]。 ### 示例代码 以下是一个简单的 WebFlux 控制器示例: ```java import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; @RestController @RequestMapping("/api") public class SampleController { @GetMapping("/hello") public Mono<String> sayHello() { return Mono.just("Hello, WebFlux!"); } } ``` ### WebClient 的使用 WebClient 是 Spring WebFlux 提供的一个用于发送 HTTP 请求的客户端。它支持同步和异步调用,并且可以轻松处理响应式数据流。 #### 发送 GET 请求 ```java import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; public class WebClientExample { private final WebClient webClient = WebClient.create("https://api.example.com"); public Mono<String> fetchData() { return webClient.get() .uri("/data") .retrieve() .bodyToMono(String.class); } } ``` #### 发送 POST 请求 ```java public Mono<String> postData(String data) { return webClient.post() .uri("/submit") .bodyValue(data) .retrieve() .bodyToMono(String.class); } ``` ### 错误处理 WebClient 提供了多种方式来处理错误,包括 HTTP 错误和非 HTTP 错误。 #### HTTP 错误处理 ```java public Mono<String> fetchDataWithHandling() { return webClient.get() .uri("/data") .retrieve() .onStatus(HttpStatus::is4xxClientError, clientResponse -> { return Mono.error(new RuntimeException("Client error")); }) .bodyToMono(String.class); } ``` #### 非 HTTP 错误处理 ```java public Mono<String> fetchDataWithNonHttpHandling() { return webClient.get() .uri("/data") .retrieve() .bodyToMono(String.class) .doOnError(throwable -> { // Handle non HTTP errors System.err.println("Error occurred: " + throwable.getMessage()); }); } ``` ### 同步与异步调用 WebClient 支持同步和异步调用。`block()` 方法用于同步调用,而 `subscribe()` 方法用于异步调用。 ```java // 同步调用 String result = fetchData().block(); // 异步调用 fetchData().subscribe(result -> { System.out.println("Received data: " + result); }); ``` ### 高级配置 WebClient 还支持高级配置,如超时与重试、连接池管理等。这些配置可以通过 `WebClient.Builder` 来实现。 ```java import org.springframework.http.client.reactive.ReactorClientHttpConnector; import io.netty.channel.ChannelOption; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.netty.http.client.HttpClient; public class WebClientAdvancedConfig { private final WebClient webClient; public WebClientAdvancedConfig() { HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .doOnConnected(conn -> conn .addHandlerLast(new ReadTimeoutHandler(5000)) .addHandlerLast(new WriteTimeoutHandler(5000))); this.webClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); } } ``` ### 相关问题 1. 如何在 Spring WebFlux 中处理表单数据? 2. 如何在 Spring WebFlux 中实现 WebSocket 通信? 3. 如何在 Spring WebFlux 中集成 Spring Security? 4. 如何在 Spring WebFlux 中处理异常? 5. 如何在 Spring WebFlux 中使用 WebClient 发送文件上传请求?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值