Java Reactor 入门指南

188 阅读4分钟

Reactor 是一个用于 Java 的反应式编程库,基于反应式流规范 (Reactive Streams Specification) 构建。Reactor 提供了高效、可组合且非阻塞的数据流。在本文中,我们将了解 Reactor 的核心概念和用法,以及如何通过一个简单的示例来实现。

核心概念

Reactor 主要有以下几个核心概念:

  1. Publisher: 数据源,用于发布元素。
  2. Subscriber: 订阅者,用于接收和处理来自 Publisher 的数据。
  3. Subscription: 订阅关系,用于连接 Publisher 和 Subscriber,允许请求和取消订阅。
  4. Processor: 处理器,既是 Publisher 又是 Subscriber,可用于在数据流中进行转换和处理。

在 Reactor 中,有两种类型的发布者(Publisher):

  • Flux: 发布 0 到 N 个元素的异步序列。
  • Mono: 发布 0 或 1 个元素的异步序列。

代码示例

接下来,我们通过一个简单的示例来了解 Reactor 的基本用法。在这个示例中,我们将创建一个 Flux,用于发布字符串,并将每个字符串转换为大写。

引入依赖

首先,我们需要在项目中引入 Reactor 相关的依赖。在 Maven 项目的 pom.xml 文件中添加以下依赖:

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

创建 Flux

创建一个 Flux 对象,用于发布一系列字符串:

import reactor.core.publisher.Flux;

public class ReactorExample {
  public static void main(String[] args) {
    Flux<String> flux = Flux.just("Hello", "Reactor", "World");
  }
}

转换数据

使用 map 操作符将每个字符串转换为大写:

Flux<String> upperCaseFlux = flux.map(String::toUpperCase);

订阅数据流

创建一个订阅者,用于接收并处理 Flux 内的字符串:

upperCaseFlux.subscribe(System.out::println);

完整示例

将以上代码放在一起,我们得到以下完整示例:

import reactor.core.publisher.Flux;

public class ReactorExample {
  public static void main(String[] args) {
    Flux<String> flux = Flux.just("Hello", "Reactor", "World");
    Flux<String> upperCaseFlux = flux.map(String::toUpperCase);
    upperCaseFlux.subscribe(System.out::println);
  }
}

执行这个示例,输出如下:

HELLO
REACTOR
WORLD

在某些情况下,你可能需要多次订阅并消费 MonoFlux 中的数据。为了实现这一目标,你可以使用 cache 操作符来缓存数据流,并在需要时进行重复消费。

高级用法 - 使用 cache 操作符

cache 操作符会缓存发射的数据,并在每次订阅时重复发射这些数据。这样,在多次订阅时,数据源不会被多次访问。

对于 Mono

import reactor.core.publisher.Mono;

public class MonoDemo {
    public static void main(String[] args) {
        Mono<String> cachedMono = Mono.fromCallable(() -> {
            System.out.println("Calling data source");
            return "Hello, World!";
        }).cache();

        cachedMono.subscribe(System.out::println); // 输出:Calling data source 和 Hello, World!
        cachedMono.subscribe(System.out::println); // 输出:Hello, World!
    }
}

对于 Flux

import reactor.core.publisher.Flux;

public class FluxDemo {
    public static void main(String[] args) {
        Flux<String> cachedFlux = Flux.fromIterable(() -> {
            System.out.println("Calling data source");
            return List.of("A", "B", "C");
        }).cache();

        cachedFlux.subscribe(System.out::println); // 输出:Calling data source 和 A, B, C
        cachedFlux.subscribe(System.out::println); // 输出:A, B, C
    }
}

在这两个示例中,我们使用了 cache 操作符来缓存 MonoFlux 的数据。当我们订阅 cachedMonocachedFlux 时,原始数据源只会被访问一次,后续订阅将直接使用缓存的数据。

需要注意的是,使用 cache 操作符会将数据保存在内存中,因此对于大量数据或长时间存储,你需要考虑内存使用情况。另外,cache 操作符缓存的数据是不可变的,这意味着如果你修改了缓存中的数据,所有后续的订阅者都会看到修改后的数据。

高级用法 - 使用 defer 操作符

当您需要为每个订阅者重新计算 MonoFlux 的数据,而不是使用缓存数据时,您可以使用 defer 操作符。defer 接收一个返回 MonoFlux 的函数,并在每次订阅时重新执行该函数。这样,每个订阅者都会获得独立的数据流。

对于 Mono

import reactor.core.publisher.Mono;

public class MonoDeferDemo {
    public static void main(String[] args) {
        Mono<String> sourceMono = Mono.fromCallable(() -> {
            System.out.println("Calling data source");
            return "Hello, World!";
        });

        Mono<String> deferredMono = Mono.defer(() -> sourceMono);

        deferredMono.subscribe(System.out::println); // 输出:Calling data source 和 Hello, World!
        deferredMono.subscribe(System.out::println); // 输出:Calling data source 和 Hello, World!
    }
}

对于 Flux

import reactor.core.publisher.Flux;

public class FluxDeferDemo {
    public static void main(String[] args) {
        Flux<String> sourceFlux = Flux.fromIterable(() -> {
            System.out.println("Calling data source");
            return List.of("A", "B", "C");
        });

        Flux<String> deferredFlux = Flux.defer(() -> sourceFlux);

        deferredFlux.subscribe(System.out::println); // 输出:Calling data source 和 A, B, C
        deferredFlux.subscribe(System.out::println); // 输出:Calling data source 和 A, B, C
    }
}

使用 defer 操作符,每次订阅 deferredMonodeferredFlux 时,原始数据源 sourceMonosourceFlux 都会被重新访问。这样,每个订阅者都会得到独立的数据流,并且数据源可以根据需要多次计算。

请注意,这种方法可能会增加数据源的负载,因为每次订阅都会触发数据源的访问。确保您的数据源可以处理多次访问,并在需要时采取适当的缓存策略。

总结

在本文中,我们了解了 Reactor 的基本概念和用法。Reactor 提供了一种高效、可组合且非阻塞的方式来处理数据流。通过使用 Reactor,开发人员可以更容易地构建响应式应用程序,实现高性能和可扩展性。