响应式操作实战案例

【从入门到实战】jQuery与Bootstrap高效结合:打造响应式网页的终极指南 在前端开发中,jQuery和Bootstrap是两大重要工具。jQuery以“Write Less, Do More”为核心理念,简化了DOM操作和事件处理,支持链式调用和丰富的动画效果。Bootstrap则通过响应式栅格系统和丰富的组件,帮助开发者快速构建用户界面。本文详细解析了jQuery的核心知识点,包括基础语法、DOM操作、事件处理和特效动画,并结合Bootstrap的响应式布局和常用组件,展示了如何高效整合两者。 阅读详情

Project Reactor 框架

在Spring Boot 项目 Maven 中添加依赖管理。

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
</dependency>

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>

如果想在Spring Boot 项目中使用 Reactor,那么你需要在构建中设置 Reactor 的 BOM(物料清单)。下面的依赖管理条目增加了 Reactor 的 Bismuth-RELEASE 到构建中:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-bom</artifactId>
            <version>Bismuth-RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Reactor 异步数据序列

       响应式流规范的基本组件是一个异步的数据序列,在 Reactor 框架中,我们可以把这个异步数据序列表示成如下形式:

上面的异步数据序列可以用下面的公式来表示:

onNext x 0..N [onError | onComplete]
  • onNext:表示正常的包含元素的消息通知

  • onComplete:表示序列结束的消息通知

  • onError:表示序列出错的消息通知

        当触发这些消息通知时,异步序列的订阅者中对应的这三个同名方法将被调用。正常情况下,onNext() 和 onComplete() 方法都应该被调用,用来正常消费数据并结束序列。如果没有调用 onComplete() 方法就会生成一个无界数据序列,在业务系统中,这通常是不合理的。而 onError() 方法只有序列出现异常时才会被调用。 

       基于上述异步数据序列,Reactor 框架提供了两个核心组件来发布数据,分别是 Flux 和 Mono 组件。这两个组件可以说是应用程序开发过程中最基本的编程对象,这两个组件非常重要,理解清楚它两,Reactor 响应式编程才算进入门槛。

 Flux 和 Mono 组件

Flux 代表的是一个包含 0 到 n 个元素的异步序列,如下:

  • Flux 是一个标准 Publisher,表示0到N个发射项的异步序列,可选地以完成信号或错误终止。与 Reactive Streams 规范中一样,这三种类型的信号转换为对下游订阅者的 onNext、onComplete 或 onError 方法的调用。

  • 在这种大范围的可能信号中,Flux 是通用的 reactive 类型。注意,所有事件,甚至终止事件,都是可选的:没有 onNext 事件,但是 onComplete 事件表示一个空的有限序列,但是移除 onComplete 并且你有一个无限的空序列(除了关于取消的测试之外,没有特别有用)。同样,无限序列不一定是空的。例如,Flux.interval(Duration) 产生一个 Flux,它是无限的,从时钟发出规则的数据。

Mono 代表的是一个包含 0 到 1 个元素的异步序列,如下:

  • Mono 是一个专门的 Publisher,它最多发出一个项,然后可选地以 onComplete 信号或 onError 信号结束。

  • 它只提供了可用于 Flux 的操作符的子集,并且一些操作符(特别是那些将 Mono 与另一个发布者组合的操作符)切换到 Flux。

  • 例如,Mono#concatWith(Publisher) 返回一个 Flux ,而 Mono#then(Mono) 则返回另一个 Mono。

  • 注意,Mono 可以用于表示只有完成概念(类似于Runnable)的无值异步进程。若要创建一个,请使用 Mono。

响应式操作实战 

通过 Flux 对象创建响应式流

主要有以下两大类:

  • 基于各种工厂模式的静态创建方法;

  • 采用编程的方式动态创建 Flux;

 基于各种工厂模式的静态创建方法 

Reactor 中静态创建 Flux 的方法常见的包括 just()、range()、interval() 以及各种以 from- 为前缀的方法组等。

  • just() 方法

它可以指定序列中包含的全部元素,创建出来的 Flux 序列在发布这些元素之后会自动结束。一般情况下,在已知元素数量和内容时,使用 just() 方法是创建 Flux 的最简单直接的做法。 

Flux.just("Apple", "Orange", "Grape", "Banana","Strawberry")
    .subscribe(System.out::println);

 控制台输出:

Apple
Orange
Grape
Banana
Strawberry

 这里要想控制台有输出,必须要调用 subscribe 方法,Flux 要是没有订阅者,数据就不会流动。以花园软管的思路进行类比,你已经把软管接到出水口了,另一端就是从自来水公司流出的水。但是水不会流动,除非你打开水龙头。对响应式类型的订阅就是打开数据流的方式。

subscribe() 中的 lambda 表达式实际上是 java.util.Consumer,用于创建响应式流的 Subscriber。由于调用了 subscribe() 方法,数据开始流动了。在这个例子中,不存在中间操作,因此数据直接从 Flux 流到了 Subscriber。

  • fromXXX() 方法组

如果我们已经有了一个数组、一个 Iterable 对象或 Stream 对象,那么就可以通过 Flux 提供的 fromXXX() 方法组来从这些对象中自动创建 Flux,包括 fromArray()、fromIterable() 和 fromStream() 方法。

 

// fromArray()
String[] fruits = new String[] {"Apple", "Orange", "Grape", "Banana", "Strawberry"};
Flux.fromArray(fruits).subscribe(System.out::println);

// fromIterable()
List<String> fruitList = new ArrayList<>();
fruitList.add("Apple");
fruitList.add("Orange");
fruitList.add("Grape");
fruitList.add("Banana");
fruitList.add("Strawberry");
Flux.fromIterable(fruitList).subscribe(System.out::println);

// fromStream() 
Stream<String> fruitStream =Stream.of("Apple", "Orange", "Grape", "Banana", "Strawberry");
Flux.fromStream(fruitStream).subscribe(System.out::println);

 三个方法控制台都是输出:

Apple
Orange
Grape
Banana
Strawberry
  • range() 方法

有时你没有任何数据可供使用,只需要使用 Flux 作为计数器,发出一个随每个新值递增的数字。要创建计数器 Flux,可以使用静态 range() 方法。

Flux.range(1, 5).subscribe(System.out::println);
  • interval() 方法

在 Reactor 框架中,interval() 方法可以用来生成从 0 开始递增的 Long 对象的数据序列。通过 interval() 所具备的一组重载方法,我们可以分别指定这个数据序列中第一个元素发布之前的延迟时间,以及每个元素之间的时间间隔。

图中每个元素发布时相当于添加了一个定时器的效果。使用 interval() 方法的示例代码如下所示:

Flux.interval(Duration.ofSeconds(2), Duration.ofMillis(200)).subscribe(System.out::println);

 这段代码的执行效果相当于在等待 2 秒钟之后,生成一个从 0 开始逐一递增的无界数据序列,每 200 毫秒推送一次数据。

  • empty()、error() 和 never()

我们可以分别使用 empty()、error() 和 never() 这三个方法类创建一些特殊的数据序列。其中,如果你希望创建一个只包含结束消息的空序列,那么可以使用 empty() 方法,使用示例如下所示。显然,这时候控制台应该没有任何的输出结果。

Flux.empty().subscribe(System.out::println);

然后,通过 error() 方法可以创建一个只包含错误消息的序列。如果你不希望所创建的序列不发出任何类似的消息通知,也可以使用 never() 方法实现这一目标。当然,这几个方法都比较少用,通常只用于调试和测试。

不难看出,静态创建 Flux 的方法简单直接,一般用于生成那些事先已经定义好的数据序列。而如果数据序列事先无法确定,或者生成过程中包含复杂的业务逻辑,那么就需要用到动态创建方法。

 采用编程的方式动态创建 Flux

动态创建 Flux 所采用的就是以编程的方式创建数据序列,最常用的就是 generate() 方法和 create() 方法。

  • generate() 方法

generate() 方法生成 Flux 序列依赖于 Reactor 所提供的 SynchronousSink 组件,定义如下:

public static <T> Flux<T> generate(Consumer<SynchronousSink<T>> generator)

 SynchronousSink 组件包括 next()、complete() 和 error() 这三个核心方法。从 SynchronousSink 组件的命名上就能知道它是一个同步的 Sink 组件,也就是说元素的生成过程是同步执行的。这里要注意的是 next() 方法只能最多被调用一次。使用 generate() 方法创建 Flux 的示例代码如下:

Flux.generate(sink -> {
    sink.next("splendor.s");
    sink.complete();
}).subscribe(System.out::println);

运行代码控制台会打印“splendor.s”,我们在这里调用了一次 next() 方法,并通过 complete() 方法结束了这个数据流,如果不调用 complete() 方法,那么就会生成一个所有元素均为“splendor.s”的无界数据流。

如果想要在序列生成过程中引入状态,那么可以使用如下所示的 generate() 方法重载。

Flux.generate(() -> 1, (i, sink) -> {
    sink.next(i);
    if (i == 5) {
        sink.complete();
    }
    return ++i;
}).subscribe(System.out::println);

这里引入了一个代表中间状态的变量 i,然后根据 i 的值来判断是否终止序列。显然,以上代码的执行效果会在控制台中输入 1 到 5 这 5 个数字。 

  • create()

我们再来看下 create() 方法,定义如下:

public static <T> Flux<T> create(Consumer<? super FluxSink<T>> emitter)

FluxSink 除了 next()、complete() 和 error() 这三个核心方法外,还定义了背压策略,并且可以在一次调用中产生多个元素。使用 create() 方法创建 Flux 的示例代码如下:

Flux.create(sink -> {
    for (int i = 0; i < 5; i++) {
        sink.next("splendor.s" + i);
    }
    sink.complete();
}).subscribe(System.out::println);

运行代码控制台会打印“微splendor.s 0”到“splendor.s 4”的5个数据,通过 create() 方法创建 Flux 对象的方式非常灵活。 

通过 Mono 对象创建响应式流

对于 Mono 而言,可以认为它是 Flux 的一种特例,所以很多创建 Flux 的方法同样适用。

针对静态创建 Mono 的场景,前面给出的 just()、empty()、error() 和 never() 等方法同样适用。除了这些方法之外,比较常用的还有 justOrEmpty() 等方法。

justOrEmpty() 方法会先判断所传入的对象中是否包含值,只有在传入对象不为空时,Mono 序列才生成对应的元素,该方法示例代码如下:

Mono.justOrEmpty(Optional.of("splendor.s")).subscribe(System.out::println);

如果要想动态创建 Mono,我们同样也可以通过 create() 方法并使用 MonoSink 组件,示例代码如下:

Mono.create(sink -> sink.success("splendor.s")).subscribe(System.out::println);

 操作符分类

Reactor 库中的 API 提供了一组丰富的操作符,这些操作符为响应式流规范提供了很大的一个便利性。但 Reactor 中所提供的操作符数量众多,这里只针对几类具有代表性的操作符来讨论。

我将 Flux 和 Mono 操作符分成如下六大类型:

  • 转换(Transforming)操作符,负责将序列中的元素转变成另一种元素;

  • 过滤(Filtering)操作符,负责将不需要的数据从序列中剔除出去;

  • 组合(Combining)操作符,负责将序列中的元素进行合并、连接和集成;

  • 条件(Conditional)操作符,负责根据特定条件对序列中的元素进行处理;

  • 裁剪(Reducing)操作符,负责对序列中的元素执行各种自定义的裁剪操作;

  • 工具(Utility)操作符,负责一些针对流式处理的辅助性操作。

 其中,前面三种操作符统称为“转换类”操作符,剩余的三大类统称为“裁剪类”操作符。

转换类操作符 

    转换类操作符在我们编码的时候最常见了,比如 buffer、window、map 和 flatMap 等。

  • buffer 操作符

buffer 操作符的作用相当于把当前流中的元素统一收集到一个集合中,并把这个集合对象作为新的数据流。使用 buffer 操作符在进行元素收集时,可以指定集合对象所包含的元素的最大数量。

 给定一个 String 值的 Flux,每个值都包含一个水果的名称,你可以创建一个新的 List 集合的 Flux,其中每个 List 的元素数不超过指定的数目。

Flux<String> fruitFlux = Flux.just("Apple", "Orange", "Grape", "Banana", "Strawberry");
fruitFlux.buffer(3).subscribe(System.out::println);

运行代码控制台会打印:

["Apple", "Orange", "Grape"]
["Banana", "Strawberry"]
  • window 操作符

window 操作符的作用类似于 buffer,不同的是 window 操作符是把当前流中的元素收集到另外的 Flux 序列中,而不是一个集合。因此该操作符的返回值类型就变成了 Flux<Flux>。window 操作符相对比较复杂,如下图:

 上图比较复杂,代表的是一种对序列进行开窗的操作。我们来看下代码方便理解:

Flux.range(1, 5).window(2).toIterable().forEach(w -> {
    w.subscribe(System.out::println);
    System.out.println("-------");
});

这里我们生成了 5 个元素,然后通过 window 操作符把这 5 个元素转变成 3 个 Flux 对象。在将这些 Flux 对象转化为 Iterable 对象后,通过 forEach() 循环打印出来,运行代码控制台会打印:

1
2
-------
3
4
-------
5
  • map 操作符

map 操作符相当于一种映射操作,它对流中的每个元素应用一个映射函数从而达到转换效果,比较简单,我们来看一下示例。

Flux.just(1, 2).map(i -> "number-" + i).subscribe(System.out::println);

运行代码控制台会打印:

number-1
number-2

关于 map() 的重要理解是,映射是同步执行的,因为每个项都是由源 Flux 发布的。如果要异步执行映射,应考虑使用 flatMap() 操作。 

  • flatMap 操作符

flatMap 操作符执行的也是一种映射操作,但与 map 不同,该操作符会把流中的每个元素映射成一个流而不是一个元素,flatMap() 不是简单地将一个对象映射到另一个对象,而是将每个对象映射到一个新的 Mono 或 Flux。Mono 或 Flux 的结果被压成一个新的 Flux。当与subscribeOn() 一起使用时,flatMap() 可以释放 Reactor 类型的异步能力。然后再把得到的所有流中的元素进行合并,整个过程的流程图请看下图:

Flux.just(1, 5)
   .flatMap(x -> Mono.just(x * x))
   .subscribe(System.out::println);

效果如下:

1
25

 事实上,flatMap 可以对任何你感兴趣的操作进行转换。例如,在系统开发过程中,我们经常会碰到对从数据库查询所获取的数据项逐一进行处理的场景,这时候就可以充分利用 flatMap 操作符的特性开展相关操作。

如下所示的代码演示了针对从数据库获取的 User 数据,如何使用该操作符逐一查询 User 所生成的订单信息的实现方法。

Flux<User> users = userRepository.getUsers();
users.flatMap(u -> getOrdersByUser(u))

过滤操作符

  • filter 操作符 

filter 操作符其实跟 Java8  里的 filter 方法类似,对流中的元素过滤,而过滤条件的指定一般是通过断言。 

Flux.range(1, 10).filter(i -> i % 2 == 0).subscribe(System.out::println);

 比如过滤出1到10这10个元素是偶数的数出来,其中“i % 2 == 0”代表的就是一种断言。

  • first/last 操作符

first 操作符的执行效果为返回流中的第一个元素,而 last 操作符的执行效果即返回流中的最后一个元素。

  • skip/skipLast

如果使用 skip 操作符,将会忽略数据流的前 n 个元素。类似的,如果使用 skipLast 操作符,将会忽略流的最后 n 个元素。

  • take/takeLast

take 系列操作符用来从当前流中提取元素。我们可以按照指定的数量来提取元素,也可以按照指定的时间间隔来提取元素。类似的,takeLast 系列操作符用来从当前流的尾部提取元素。

组合操作符 

Reactor 中常用的组合操作符有 then/when、merge、startWith 和 zip 等,组合操作符会比过滤操作符复杂一点。

  • then/when 操作符

then 操作符的含义是等到上一个操作完成再进行下一个。

Flux.just(1, 2, 3)
    .then()
    .subscribe(System.out::println);

 这里尽管生成了一个包含 1、2、3 三个元素的 Flux 流,但 then 操作符在上游的元素执行完成之后才会触发新的数据流,也就是说会忽略所传入的元素,所以上述代码在控制台上实际并没有任何输出。

和 then 一起的还有一个 thenMany 操作服务,具有同样的含义,但可以初始化一个新的 Flux 流。示例代码如下所示,这次我们会看到控制台上输出了 4 和 5 这两个元素。

Flux.just(1, 2, 3)
    .thenMany(Flux.just(4, 5))
    .subscribe(System.out::println);

 对应的,when 操作符的含义则是等到多个操作一起完成。如下代码很好地展示了 when 操作符的实际应用场景。

public Mono<Void> updateOrders(Flux<Order> orders) {
    return orders
        .flatMap(file -> {
            Mono<Void> saveOrderToDatabase = ...;
            Mono<Void> sendMessage = ...;
            return Mono.when(saveOrderToDatabase, sendMessage);
   });
}

 假设我们对订单列表进行批量更新,首先把订单数据持久化到数据库,然后再发送一条通知类的消息。我们需要确保这两个操作都完成之后方法才能返回,所以用到了 when 操作符。

  • merge 操作符

merge 操作符用来把多个 Flux 流合并成一个 Flux 序列,而合并的规则就是按照流中元素的实际生成的顺序进行。

 我们通过 Flux.intervalMillis() 方法分别创建了两个 Flux 序列,然后将它们 merge 之后打印出来。

Flux.merge(Flux.intervalMillis(0, 100).take(2), Flux.intervalMillis(50, 100).take(2))
    .toStream().forEach(System.out::println);

 请注意,这里的第一个 intervalMillis 方法没有延迟,每隔 100 毫秒生成一个元素,而第二个 intervalMillis 方法则是延迟 50 毫秒之后才发送第一个元素,时间间隔同样是 100 毫秒。相当于两个数据序列会交错地生成数据,并合并在一起。所以以上代码的执行效果如下所示:

0
0
1
1

 和 merge 类似的还有一个 mergeSequential 方法。不同于 merge 操作符,mergeSequential 操作符则按照所有流被订阅的顺序,以流为单位进行合并。现在我们来看一下这段代码,这里仅仅将 merge 操作换成了 mergeSequential 操作。

Flux.mergeSequential (Flux.intervalMillis(0, 100).take(2), Flux.intervalMillis(50, 100).take(2))
    .toStream().forEach(System.out::println);

执行以上代码,我们将得到不同的结果,如下所示:

0
1
0
1

显然从结果来看,mergeSequential 操作是等上一个流结束之后再 merge 新生成的流元素。

  • zip 操作符

上面的 merge 操作符合并后的 Flux 发出的数据的顺序,与源发出的数据的时间顺序一致。由于两个 Flux 都被设置为固定频率发送数据,因此值会通过合并后的 Flux 交替出现 —— a…b…a…b 一直这样下去。如果其中任何一个 Flux 的发送时间被修改了的话,你可能会看到 2 个 a 跟在 1 个 b 后面或是 2 个 b 跟在 1 个 a 后面的情况。

因为 merge 不能保证源之间的完美交替,所以可能需要考虑使用 zip() 操作。当两个 Flux 对象压缩在一起时,会产生一个新的 Flux,该 Flux 生成一个元组,其中元组包含来自每个源 Flux 的一个项。下图说明了如何将两个 Flux 对象压缩在一起:

 使用 zip 操作符在合并时可以不做任何处理,由此得到的是一个元素类型为 Tuple2 的流,示例代码如下所示:

Flux flux1 = Flux.just(1, 2);
Flux flux2 = Flux.just(3, 4);
Flux.zip(flux1, flux2).subscribe(System.out::println);

执行效果如下:

[1,3]
[2,4]

我们可以使用 zipWith 操作符实现同样的效果,示例代码如下所示:

Flux.just(1, 2).zipWith(Flux.just(3, 4))
    .subscribe(System.out::println);

条件操作符

  • defaultIfEmpty 操作符

defaultIfEmpty 操作符针对空数据流提供了一个简单而有用的处理方法。该操作符用来返回来自原始数据流的元素,如果原始数据流中没有元素,则返回一个默认元素。

@GetMapping("/orders/{id}")
public Mono<ResponseEntity<Order>> findOrderById(@PathVariable String id) {
     return orderService.findOrderById(id)
         .map(ResponseEntity::ok)
         .defaultIfEmpty(ResponseEntity.status(404).body(null));
}

 可以看到,这里使用 defaultIfEmpty 操作符实现默认返回值。在示例代码所展示的 HTTP 端点中,当找不到指定的数据时,我们可以通过 defaultIfEmpty 方法返回一个空对象以及 404 状态码。

  • takeUntil/takeWhile 操作符

takeUntil 操作符的基本用法是 takeUntil (Predicate predicate),其中 Predicate 代表一种断言条件,该操作符将从数据流中提取元素直到断言条件返回 true。

takeUntil 的示例代码如下所示,我们希望从一个包含 100 个连续元素的序列中获取 1~10 个元素。

Flux.range(1, 100).takeUntil(i -> i == 10)
    .subscribe(System.out::println);

类似的,takeWhile 操作符的基本用法是 takeWhile (Predicate continuePredicate),其中 continuePredicate 代表的也是一种断言条件。与 takeUntil 不同的是,takeWhile 会在 continuePredicate 条件返回 true 时才进行元素的提取。takeWhile 的示例代码如下所示,这段代码的执行效果与 takeUntil 的示例代码一致。

Flux.range(1, 100).takeWhile(i -> i <= 10)
    .subscribe(System.out::println);
  • skipUntil/skipWhile 操作符

与 takeUntil 相对应,skipUntil 操作符的基本用法是 skipUntil (Predicate predicate)。skipUntil 将丢弃原始数据流中的元素直到 Predicate 返回 true。

同样,与 takeWhile 相对应,skipWhile 操作符的基本用法是 skipWhile (Predicate continuePredicate),当 continuePredicate 返回 true 时才进行元素的丢弃。

裁剪操作符 

裁剪操作符通常用于统计流中的元素数量,或者检查元素是否具有一定的属性。在 Reactor 中,常用的裁剪操作符有 any 、concat、count 和 reduce 等。

  • any 操作符

any 操作符用于检查是否至少有一个元素具有所指定的属性,代码如下:

Flux.just(3, 5, 7, 9, 11, 15, 16, 17)
    .any(e -> e % 2 == 0)
    .subscribe(isExisted -> System.out.println(isExisted));

在这个 Flux 流中存在一个元素 16 可以被 2 除尽,所以控制台将输出“true”。

  • concat 操作符

在这个 Flux 流中存在一个元素 16 可以被 2 除尽,所以控制台将输出“true”。

  • concat 操作符

concat 操作符用来合并来自不同 Flux 的数据。与上面所介绍的 merge 操作符不同,这种合并采用的是顺序的方式,所以严格意义上并不是一种合并操作,所以我们把它归到裁剪操作符类别中。

Flux.concat(
    Flux.range(1, 3),
    Flux.range(4, 2),
    Flux.range(6, 5)
).subscribe(System.out::println);

我们将在控制台中依次看到 1 到 10 这 10 个数字。

  • reduce 操作符

裁剪操作符中最经典的就是这个 reduce 操作符。reduce 操作符对来自 Flux 序列中的所有元素进行累积操作并得到一个 Mono 序列,该 Mono 序列中包含了最终的计算结果。reduce 操作符示意图如下所示:

这里的 BiFunction 就是一个求和函数,用来对 1 到 10 的数字进行求和,运行结果为 55。

Flux.range(1, 10).reduce((x, y) -> x + y)
    .subscribe(System.out::println);

与 reduce 操作符类似的还有一个 reduceWith 操作符,用来在 reduce 操作时指定一个初始值。reduceWith 操作符的代码示例如下所示,我们使用 5 来初始化求和过程,显然得到的结果将是 60。

Flux.range(1, 10).reduceWith(() -> 5, (x, y) -> x + y)
    .subscribe(System.out::println);

 工具操作符

Reactor 中常用的工具操作符有 subscribe、timeout、block、log 和 debug 等。

  • subscribe 操作符

 subscribe 操作符用使用最多,接下来看下它的 API。 

/订阅流的最简单方法,忽略所有消息通知
subscribe();

//对每个来自 onNext 通知的值调用 dataConsumer,但不处理 onError 和 onComplete 通知
subscribe(Consumer<T> dataConsumer);

//在前一个重载方法的基础上添加对 onError 通知的处理
subscribe(Consumer<T> dataConsumer, Consumer<Throwable> errorConsumer);

//在前一个重载方法的基础上添加对 onComplete 通知的处理
subscribe(Consumer<T> dataConsumer, Consumer<Throwable> errorConsumer, Runnable completeConsumer);

//这种重载方法允许通过请求足够数量的数据来控制订阅过程
subscribe(Consumer<T> dataConsumer, Consumer<Throwable> errorConsumer, Runnable completeConsumer, Consumer<Subscription> subscriptionConsumer);

//订阅序列的最通用方式,可以为我们的 Subscriber 实现提供所需的任意行为
subscribe(Subscriber<T> subscriber);
  • timeout 操作符

timeout 操作符非常简单,保持原始的流发布者,当特定时间段内没有产生任何事件时,将生成一个异常。

  • block 操作符

 顾名思义,block 操作符在接收到下一个元素之前会一直阻塞。block 操作符常用来把响应式数据流转换为传统数据流。例如,使用如下方法将分别把 Flux 数据流和 Mono 数据流转变成普通的 List对象和单个的 Order 对象,我们同样可以设置 block 操作的等待时间。

public List<Order> getAllOrders() {
    return orderservice.getAllOrders().block(Duration.ofSecond(5));
}

public Order getOrderById(Long orderId) {
    return orderservice.getOrderById(orderId).block(Duration.ofSecond(2));
}
  • log 操作符

Reactor 中专门提供了针对日志的工具操作符 log,它会观察所有的数据并使用日志工具进行跟踪。我们可以通过如下代码演示 log 操作符的使用方法,在 Flux.just() 方法后直接添加 log() 函数。

Flux.just(1, 2).log().subscribe(System.out::println);

 以上代码的执行结果如下所示(为了显示简洁,部分内容和格式做了调整)。通常,我们也可以在 log() 方法中添加参数来指定日志分类的名称。

Info: | onSubscribe([Synchronous Fuseable] FluxArray.ArraySubscription)
Info: | request(unbounded)
Info: | onNext(1)
1
Info: | onNext(2)
2
Info: | onComplete()
Reactor响应式编程系列导航 Reactor响应式编程系列导航 Reactor响应式编程系列(一)- 初识Reactor3 阅读详情

相关推荐

C#响应式编程:从0到1掌握数据流管理!IObservable vs IObserver谁更强?

C#响应式编程实战指南(150字摘要) 本文通过快递比喻解析C#响应式编程核心机制: 数据流模型:IObservable(生产者)与IObserver(消费者)协作,支持多订阅和异步处理 核心操作: 过滤(Where)、映射(Select)实现数据转换 合并(Merge)处理多数据源 实战陷阱: 必须调用Dispose()避免内存泄漏 Subject兼具生产/消费能力,适合桥接场景 典型应用:实时数据监控、事件驱动UI更新 通过代码示例演示数据流创建、订阅及生命周期管理,帮助开发者掌握响应式编程范式。 (关

java专栏 1413

projectreactor框架的操作符

project-reactor提供了大量操作符操作Flux和Mono对象 文章目录1 转换操作符1.1. buffer1.2 map(映射)和flatMap1.3 window2 过滤操作符3 组合操作符4 条件操作符5 数学操作符6 observable工具操作符6.1. subscribe:注册订阅逻辑6.2 delay:事件的传递向后延迟一段时间6.3 timeout:超时配置6.4 block6.5 日志和调试操作符 1 转换操作符 只要是buffer,map,flatMap,window等 1.1

wyaoyao93的博客 816

响应式拿手好道-RxJava-RxAndroid实战指南

总的来说,掌握了RxJava/RxAndroid这一宝贵的响应式编程利器,必将使我们在移动开发的道路上阔步前行,逐步解锁高阶异步编程的大门。当然,这绝非止步之路,Kotlin协程等新兴技术的涌现,也将给响应式编程领域注入新的活力。保持开放的学习心态,我们定能驾驭更多先进工具,创造出更卓越的作品。

w风雨无阻w的专栏 1085

Project Reactor 常用 API 总结

Reactor 是完全异步非阻塞的响应式编程 JVM 框架,它能通过背压的方式来实现对元素需求的管理。它提供了跟Java8 函数式接口的集成,例如 CompletableFuture, Stream 和 Duration;并主要提供两个异步序列接口Flux(N个元素) 和Mono(0-1个元素),并严格遵循响应式流的JVM规范。

龚小帅的博客 1870

第三方库与工具:响应式编程RxJava与Flow原理剖析

定义与特点数据流驱动声明式编程异步非阻塞背压处理核心概念操作符线程调度错误处理响应式编程的核心概念RxJava和Flow的实现原理两种框架的实际应用场景性能优化和最佳实践根据场景选择合适的框架注意内存和性能优化合理处理背压问题保持代码的可维护性响应式编程是Android开发中不可或缺的一部分,掌握RxJava和Flow不仅能够提高开发效率,还能写出更加优雅和健壮的代码。希望通过本文的学习,能够帮助你更好地理解和使用这两个强大的框架。

Ya-Jun 的博客 231

4.4 自定义响应式工具函数类型标注

4.4 自定义响应式工具函数类型标注

嗨,欢迎来到我的 优快云 博客小天地!一名深耕多年的技术发烧友。在这里,我将把日常工作中积累的宝贵经验,从复杂架构设计的精妙之处,到代码优化的实战技巧,毫无保留地分享给大家。 185

使用 Reactor 进行反应式编程

反应式编程(Reactive Programming)这种新的编程范式越来越受到开发人员的欢迎。在 Java 社区中比较流行的是 RxJava 和 RxJava 2。本文要介绍的是另外一个新的反应式编程库 Reactor。 反应式编程介绍 反应式编程来源于数据流和变化的传播,意味着由底层的执行模型负责通过数据流来自动传播变化。比如求值一个简单的表达式 c=a+b,当 a 或者 b 的值发生变化...

weixin_42073629的博客 874

前端领域如何用Less实现响应式设计

本文旨在为前端开发者提供一套完整的Less实现响应式设计的解决方案。Less核心特性在响应式设计中的应用响应式设计的基本原则和最佳实践使用Less简化媒体查询和断点管理性能优化和代码组织策略文章首先介绍Less和响应式设计的基本概念,然后深入探讨如何结合两者。我们将通过实际代码示例展示核心技巧,最后讨论实际应用场景和工具推荐。Less: 一种CSS预处理器,扩展了CSS语言,增加了变量、混合、函数等特性响应式设计: 使网页能够根据设备特性(如屏幕尺寸)自动调整布局的设计方法。

AI架构全栈开发实战笔记 661

六种常用事务解决方案,你方唱罢,我登场(没有最好只有更好)

在分布式系统中,为了保证数据的高可用,通常,我们会将数据保留多个副本(replica),这些副本会放置在不同的物理的机器上。为了对用户提供正确的 CRUD 等语义,我们需要保证这些放置在不同物理机器上的副本是一致的。分布式事务在现在遍地都是分布式部署的系统中几乎是必要的。我们的项目用到了数据库,也和事务有关,我们先分析一下项目的问题,再描述一下事务。如上图,如果用户打车成功,需要修改司机状态、下单、记录支付日志,而每个操作都是调用了不同的服务,比如此时服务执行成功了,但是有可能执行失败了,这时候如何实现跨服

chenxuyuana的博客 443

前端领域 Sass 与响应式设计

随着互联网的发展,各种设备层出不穷,为了给用户提供一致且良好的浏览体验,响应式设计成为前端开发的重要需求。Sass 作为一种强大的 CSS 预处理器,为响应式设计提供了更高效、灵活的解决方案。本文的目的在于深入介绍 Sass 的特性和使用方法,并阐述如何运用 Sass 实现优秀的响应式设计。范围涵盖了 Sass 的基本概念、核心算法、实际应用案例以及响应式设计的原理和实现步骤等方面。本文将按照以下结构进行阐述:首先介绍相关背景知识,包括 Sass 和响应式设计的基本概念;

AI架构全栈开发实战笔记 348

projectreactor.io:React堆公共网站

项目React堆网站 这是Project Reactor主页的源代码。 它还作为如何在单个项目中使用Spring Web Reactive Framework,Reactor和其他Web技术的示例应用程序。 您可以通过运行以下命令启动应用程序: $ ./gradlew run 要启用实时重载静态资源(包括.scss文件),请在应用程序仍在运行时运行以下命令: $ ./gradlew processResources -t 要打包应用程序,包括Javadoc,请执行以下操作: $ ./gradlew clean build shadowJar 确保您至少具有IntelliJ IDEA 2016.2.5和Kotlin插件1.1-M02, build.gradle.kts自动完成build.gradle.kts和验证。 您可能必须在以下位置配置EAP更新站点:工具-> Kotlin->配

掌握Flex布局,设计响应式界面 — YDUIbuilder教程

在YDUIbuilder,我们相信设计不仅是艺术,更是科学。现在,通过我们的Flex布局讲解教程,学习如何使用灵活的布局技术,打造既美观又实用的响应式网页!立即观看YDUIbuilder的Flex布局教程,释放你的设计潜能,打造专业级响应式界面!yduibuilder: 快速开发UI界面,原型设计即前端开发。

(黎子)人生编码 420

避坑指南:LVGL在Arduino环境编译报错?3步搞定lv_conf.h路径问题(附ST7789屏幕调试技巧)

本文详细解析了在Arduino环境中配置LVGL图形库时,如何解决常见的“lv_conf.h file not found”编译报错问题。通过三步法:定位并复制模板文件、启用核心配置并指定路径、配置基础参数,从根本上解决路径问题。文章还结合ST7789屏幕驱动,分享了使用TFT_eSPI库进行显示适配、内存优化及背光控制等实战调试技巧,帮助开发者建立清晰、可维护的嵌入式UI工程基础。

lake5的博客 860

精通高级RxJava 2响应式编程思想

第1章 课程介绍—课程整体内容介绍简单介绍本系列课程的内容,并提前布置预先需要储备的知识。第2章 响应式编程思想概述—概念与案例讲解本章节主要阐释响应式编程思想,先做一个概念性的介绍,之后会以生活中的实例和代码实例相结合的方式来讲解。第3章 RxJava基本元素—源码解析与案例实践本章节开始进入源码讲解部分,主要讲RxJava的基本元素。先简单介绍和回顾RxJava,之后进入源码分析,再以实战...

qq_42739742的博客 329

DDR核心机制解析:从Burst到Prefetch的性能跃迁

本文深入解析了DDR内存性能跃迁的核心机制,重点阐述了Burst(突发传输)与Prefetch(预取)技术的协同工作原理。Burst机制通过连续传输数据减少访问延迟,而Prefetch则通过内部预取数据提升吞吐量,两者结合完美匹配现代CPU缓存行需求,是DDR3等内存实现高效带宽的关键。

weixin_29232121的博客 133

利用李雅普诺夫函数开发航天器姿态控制系统

本文基于李雅普诺夫函数控制方法,采用修正罗德里格斯参数(MRP)描述航天器姿态,设计了姿态控制律。通过构造能量型李雅普诺夫函数,推导出保证系统稳定的控制闭环方程,实现了航天器从初始状态到目标状态的稳定转换。针对MRP参数可能出现的奇异性问题,采用影子集进行规避。仿真实验包含两种模式:非追踪模式(目标状态为0)和追踪模式(目标为三角函数轨迹)。结果表明,该控制方法能够有效实现姿态稳定和轨迹跟踪,在短暂调整后即可精确跟随目标轨迹。控制律设计考虑了不同参考系间的转换,通过DCM矩阵处理目标角速度的坐标变换问题。

weixin_50227659的博客 1221

Vue.js——数组中的响应式方法——2020.11.20

<!--作者:key--> <!--浏览工具:Chrome--> <!--开发工具:WebStorm--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <ul&gt

weixin_43402353的博客 399

Dual-stream multiple instance learning networks for tumor detection in Whole Slide Image——论文笔记

关于多示例学习的资料网上比较少,但是在医学图像方面还是有人在应用的。主要是因为一般比较大,几个G都是十分常见的,这么大图片是无法直接训练,一般都会进行切图,所以天然契合多示例学习bag和instance的概念。最近看了一篇论文刚好关于这方面的,所以做下笔记。 github地址:https://github.com/binli123/dsmil-wsi paper:https://arxiv.org/abs/2011.08939 背景知识 在多示例学习(MIL)中,一组训练样本被视为包含多个instance的

weixin_41693877的博客 4170

Android学习之响应式编程

可以看出,Android 的响应式编程范式主要是由观察者模式结合响应式流来实现的。观察者模式作为核心架构,构建起数据生产者与消费者之间的订阅关系,使得数据变化能够及时被关注;响应式流则负责承载数据的流动与处理,通过操作符对数据进行转换、过滤等操作,实现复杂业务逻辑的编排。

m0_69682018的博客 762

VSCode终端打不开?3分钟搞定Windows下Terminal配置(附最新JSON设置方法)

本文针对Windows系统下VSCode终端无法启动的常见问题,提供了详细的诊断与解决方案。核心在于更新已弃用的旧配置,采用最新的JSON设置方法,通过定义`terminal.integrated.profiles.windows`和指定`terminal.integrated.defaultProfile.windows`,快速修复并优化终端配置,实现三分钟内恢复正常使用。

flower的专栏 740

Token算力工厂建设方案:从GPU集群到计量计费的全链路设计

在AI应用从“租核”走向“买Token”的范式转换中,算力交付的核心指标已不再是vCPU或GPU实例,而是单位时间内的Token产出量与单Token成本。这一变化推动基础设施从资源租赁向计件生产演进,要求GPU集群、推理引擎、调度系统、计量计费等模块像工厂产线一样协同运转。本文从硬件异构混部、存储网络设计、推理引擎调优、Token网关与配额管理、稳定性治理等维度,系统拆解如何构建一座可量化、可计费、可扩展的Token算力工厂,并给出分三期落地的推进节奏,帮助架构师与技术管理者在AI基础设施建设中实现成本可控

weixin_29095587的博客 261

深入了解 Reactor:响应式编程的利器

Reactor是一个基于Reactive Streams 规范的响应式编程库,主要用于处理非阻塞的数据流。它是响应式流的 Java 实现之一,为开发者提供了强大的工具来构建异步、事件驱动的应用程序。Reactor 官方英文文档Reactor 中文文档异步非阻塞:数据流以事件的形式处理,避免线程阻塞。响应式背压:控制数据流速率,确保系统稳定运行。丰富的操作符:提供了数百种数据操作符,方便处理复杂数据流。与 Spring WebFlux 深度集成。

qq_36291550的博客 1478

基于航天宏图PIE-SAR软件的高分三号(GF-3)SAR影像的预处理

软件官网 航天宏图—国内领先的卫星运营与应用服务提供商(官网) 一、数据导入 数据导入-GF-3-根据模式选择 参数文件导入解压后GF-3数据的XML文件 通道自动填充 设置输出目录 确定 等待,加载图像、自动构建金字塔 二、复数数据转换 强度/幅度特征是SAR影像最主要的特征之一,基于SAR强度/幅度影像可以提取地物目标信息,因此需要将SAR复数数据转换为SAR强度/幅度数据。导入输入影像,设置参数类型,输出目录、命名、类型; 漫长的等待 三、多视 为提...

weixin_52127098的博客 9002

Less在前端响应式设计中的运用

本文将聚焦「Less如何解决响应式设计的痛点」,覆盖Less核心功能(变量、混合、嵌套)在媒体查询优化、断点统一管理、样式复用等场景的具体应用,适合从基础到进阶的前端开发者阅读。本文将从「Less与响应式的核心概念」入手,通过「早餐店调酱」的生活案例引出技术原理,再结合具体代码演示(从变量定义断点到混合封装媒体查询),最后通过「电商首页适配」实战案例验证效果,确保您学完就能用!/* 全局变量(调料架) *//* 手机断点 *//* 平板断点 */@gap: 20px;/* 商品之间的间距 */

小白菜的博客 694

自学 ABAP 和自学 SAP 的差别

有朋友在我的 ABAP 学习教程里留言: > 自学SAP是不是都要搭建虚拟服务器,然后自己去官网下载新版本SAP就可以吗 对于初学者,我们要搞清楚 ABAP 和 SAP 的区别。 ABAP 是一门编程语言,SAP 首先是德国软件公司的名称,也代表这家公司开发的一系列软件。SAP 公司开发的软件,我们有时也称产品,其功能能满足客户一个或者多个领域的业务需求。这些不同领域,我们也称为不同的业务模块,比如 ERP,CRM,SRM,HR 等等。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 6861

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

MyBatis-Plus与WebFlux集成:响应式编程下的数据库操作实践

在现代微服务架构中,响应式编程(Reactive Programming)已成为处理高并发、高吞吐量场景的重要范式。Spring WebFlux作为Spring框架的响应式Web栈,提供了非阻塞、异步的编程模型。然而,传统的MyBatis作为阻塞式ORM框架,在与WebFlux集成时面临着诸多挑战。 MyBatis-Plus作为MyBatis的增强工具包,通过巧妙的架构设计和扩展机制,为响应式编...

gitblog_00423的博客 1157

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

上一篇: SpringBoot 实现通用导出excel文件案例
下一篇: Reactor响应式流的核心机制——背压机制
splendor.s
博客等级 码龄10年 91粉丝 67原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值