响应式编程:map与flatMap实战解析

核心操作符详解与实战优化

1. mapflatMap:转换与异步展开

map 用于同步的一对一转换,而 flatMap 用于将每个元素异步映射为一个新的 PublisherMonoFlux)并展开合并结果,是处理异步依赖操作(如网络请求、数据库查询)的核心 。

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;

public class TransformOperators {

    // 场景1: map - 同步数据转换
    public Flux<String> toUpperCase(Flux<String> names) {
        return names.map(String::toUpperCase); // 同步转换,保持顺序
    }

    // 场景2: flatMap异步一对多展开 (顺序不保证)
    public Flux<String> fetchDetailsAsync(List<Integer> ids) {
        return Flux.fromIterable(ids)
                .flatMap(id -> 
                    fetchFromRemoteService(id) // 返回 Mono<String>
                            .subscribeOn(Schedulers.boundedElastic()) // 指定异步执行线程池
                );
    }

    // 场景3: concatMap - 异步一对多展开 (保证顺序)
    public Flux<String> fetchDetailsInOrder(List<Integer> ids) {
        return Flux.fromIterable(ids)
                .concatMap(id -> fetchFromRemoteService(id)); // 顺序执行,保证输出顺序与输入一致
    }

    // 场景4: flatMapSequential - 异步并发但按序输出
    public Flux<String> fetchDetailsConcurrentOrdered(List<Integer> ids) {
        return Flux.fromIterable(ids)
                .flatMapSequential(id -> 
                    fetchFromRemoteService(id).subscribeOn(Schedulers.parallel()),
                    5 // 最大并发数
                );
    }

    private Mono<String> fetchFromRemoteService(Integer id) {
        // 模拟异步网络请求
        return Mono.fromCallable(() -> {
                    Thread.sleep(100); // 模拟IO延迟
                    return "Data for ID: " + id;
                })
                .subscribeOn(Schedulers.boundedElastic()); // 在弹性线程池执行阻塞调用
    }

    public static void main(String[] args) throws InterruptedException {
        TransformOperators demo = new TransformOperators();
        List<Integer> ids = List.of(1, 2, 3, 4, 5);

        System.out.println("=== flatMap (并发,顺序不确定) ===");
        demo.fetchDetailsAsync(ids).subscribe(System.out::println);

        System.out.println("
=== concatMap (顺序执行) ===");
        demo.fetchDetailsInOrder(ids).subscribe(System.out::println);

        System.out.println("
=== flatMapSequential (并发,但输出有序) ===");
        demo.fetchDetailsConcurrentOrdered(ids).subscribe(System.out::println);

        Thread.sleep(2000); // 等待异步任务完成
    }
}

关键区别:

操作符 执行方式 顺序保证 适用场景
map 同步 严格保证 简单的数据格式转换、计算
flatMap 异步,并发 不保证 独立的异步操作,如并行API调用
concatMap 异步,顺序 严格保证 有顺序依赖的异步操作,如流水线处理
flatMapSequential 异步,并发 保证 需要并发提升性能但结果需保持原序

2. filterdistinct:数据筛选

filter 根据条件过滤元素,distinct 用于去重,常结合使用以净化数据流 。

java 复制代码
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.Objects;

public class FilteringOperators {

    // 场景1: 基础过滤与去重
    public Flux<Integer> filterAndDistinct(Flux<Integer> numbers) {
        return numbers
                .filter(n -> n % 2 == 0) // 过滤偶数
                .distinct()               // 去重
                .filter(n -> n > 10);     // 过滤大于10的数
    }

    // 场景2: 基于时间的去重 (防抖变体)
    public Flux<String> distinctUntilChangedWithDebounce(Flux<String> searchStream) {
        return searchStream
                .filter(term -> term != null && term.length() >= 2)
                .distinctUntilChanged() // 仅当与前一个元素不同时才发射
                .sample(Duration.ofMillis(300)); // 采样,实现防抖效果
    }

    // 场景3: 条件取用 takeUntil / skipWhile
    public Flux<Integer> takeUntilCondition(Flux<Integer> infiniteStream) {
        return infiniteStream
                .takeUntil(n -> n > 50); // 发射元素直到条件为真(包含使条件为真的元素)
    }

    public Flux<Integer> skipWhileCondition(Flux<Integer> numbers) {
        return numbers
                .skipWhile(n -> n < 10); // 跳过元素直到条件为假(从第一个不满足条件的元素开始发射)
    }

    public static void main(String[] args) {
        FilteringOperators demo = new FilteringOperators();

        Flux<Integer> numbers = Flux.just(1, 2, 2, 3, 4, 4, 5, 12, 12, 15);
        demo.filterAndDistinct(numbers).subscribe(n -> System.out.print(n + " "));
        // 输出: 12

        System.out.println("
---");
        Flux<String> searches = Flux.just("java", "java", "jav", "javascript", "java");
        demo.distinctUntilChangedWithDebounce(searches).subscribe(System.out::println);
        // 输出: java (连续重复的"java"被过滤) -> jav -> javascript
    }
}

3. switchMapconcatMap:高级映射与取消语义

switchMap 在收到新元素时,会取消并丢弃前一个内部 Publisher 的未完成结果,非常适合实现搜索建议、取消过时请求等场景 。concatMap 则保证顺序且不会取消。

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;

public class AdvancedMappingOperators {

    private AtomicInteger requestCounter = new AtomicInteger(0);

    // 场景1: switchMap - 用于搜索建议(取消前一个请求)
    public Flux<String> liveSearch(Flux<String> queryFlux) {
        return queryFlux
                .debounce(Duration.ofMillis(300)) // 防抖
                .distinctUntilChanged()
                .switchMap(query -> {
                    int reqId = requestCounter.incrementAndGet();
                    System.out.println("发起搜索请求[" + reqId + "]: " + query);
                    // 模拟网络请求,每个请求耗时不同
                    return simulateSearchApi(query)
                            .doOnCancel(() -> System.out.println("取消请求[" + reqId + "]: " + query));
                });
    }

    // 场景2: concatMap - 用于有严格顺序要求的异步写入
    public Flux<String> sequentialWrite(Flux<String> dataFlux) {
        return dataFlux
                .concatMap(data -> 
                    simulateWriteToDatabase(data) // 返回Mono<Void>
                            .thenReturn("写入成功: " + data) // 转换结果
                );
    }

    private Flux<String> simulateSearchApi(String query) {
        // 模拟一个延迟的API响应
        long delay = (long) (Math.random() * 500) + 100;
        return Flux.just("结果1 for " + query, "结果2 for " + query)
                .delayElements(Duration.ofMillis(delay))
                .doOnComplete(() -> System.out.println("请求完成: " + query));
    }

    private Mono<Void> simulateWriteToDatabase(String data) {
        return Mono.fromRunnable(() -> {
                    try {
                        Thread.sleep(200); // 模拟写入延迟
                        System.out.println("已写入数据库: " + data);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                })
                .subscribeOn(Schedulers.boundedElastic())
                .then();
    }

    public static void main(String[] args) throws InterruptedException {
        AdvancedMappingOperators demo = new AdvancedMappingOperators();

        System.out.println("=== switchMap 演示(快速输入,只保留最后一次) ===");
        // 模拟快速输入 "a", "ab", "abc"
        Flux<String> rapidInput = Flux.just("a", "ab", "abc")
                .delayElements(Duration.ofMillis(150)); // 间隔小于防抖时间
        demo.liveSearch(rapidInput).subscribe(
                result -> System.out.println("收到结果: " + result),
                error -> System.err.println("错误: " + error),
                () -> System.out.println("搜索流结束")
        );

        Thread.sleep(1000);
        System.out.println("
=== concatMap 演示(保证顺序) ===");
        Flux<String> data = Flux.just("记录A", "记录B", "记录C");
        demo.sequentialWrite(data).subscribe(System.out::println);

        Thread.sleep(1500);
    }
}

4. 错误处理操作符实战

响应式流的错误处理是声明式的,操作符如 onErrorReturn, onErrorResume, retry 等允许优雅地定义错误恢复策略 。

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;

public class ErrorHandlingDemo {

    private AtomicInteger callCount = new AtomicInteger(0);

    // 场景1: 基础错误恢复
    public Mono<String> fetchDataWithFallback(String id) {
        return unstableExternalService(id)
                .onErrorReturn("默认数据") // 1. 出错时返回静态值
                .onErrorResume(IllegalArgumentException.class, e -> 
                    fetchFromBackupService(id) // 2. 根据异常类型切换到备用流
                )
                .onErrorResume(e -> {
                    logError(e);
                    return Mono.error(new BusinessException("业务异常", e)); // 3. 转换异常
                });
    }

    // 场景2: 重试策略
    public Mono<String> fetchWithRetry(String id) {
        return unstableExternalService(id)
                .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) // 指数退避重试
                        .maxBackoff(Duration.ofSeconds(10))
                        .filter(throwable -> !(throwable instanceof IllegalArgumentException)) // 特定异常不重试
                        .doBeforeRetry(rs -> System.out.println("第" + rs.totalRetries() + "次重试..."))
                        .onRetryExhaustedThrow((spec, signal) -> 
                            new ServiceUnavailableException("服务不可用,重试耗尽")
                        )
                );
    }

    // 场景3: 超时控制
    public Mono<String> fetchWithTimeout(String id) {
        return unstableExternalService(id)
                .timeout(Duration.ofSeconds(2), 
                        Mono.defer(() -> Mono.just("降级数据")) // 超时后的降级
                )
                .onErrorMap(TimeoutException.class, e -> 
                    new BusinessException("请求超时", e)
                );
    }

    // 场景4: 在Flux流中处理错误(继续处理后续元素)
    public Flux<String> processBatchWithErrorContinue(List<String> ids) {
        return Flux.fromIterable(ids)
                .flatMap(id -> 
                    processItem(id)
                            .onErrorResume(e -> {
                                System.err.println("处理项 " + id + " 失败: " + e.getMessage());
                                return Mono.empty(); // 错误时返回空流,不影响其他项
                            })
                );
    }

    private Mono<String> unstableExternalService(String id) {
        return Mono.defer(() -> {
            int count = callCount.incrementAndGet();
            if (count % 3 == 0) { // 模拟每第三次调用失败
                return Mono.error(new RuntimeException("模拟服务异常"));
            }
            return Mono.just("数据来自服务: " + id)
                    .delayElement(Duration.ofMillis(500));
        });
    }

    private Mono<String> fetchFromBackupService(String id) {
        return Mono.just("数据来自备份: " + id);
    }

    private Mono<String> processItem(String id) {
        if ("error".equals(id)) {
            return Mono.error(new RuntimeException("处理错误"));
        }
        return Mono.just("已处理: " + id);
    }

    private void logError(Throwable e) {
        System.err.println("[ERROR] " + e.getMessage());
    }

    public static void main(String[] args) throws InterruptedException {
        ErrorHandlingDemo demo = new ErrorHandlingDemo();

        System.out.println("=== 测试重试与超时 ===");
        demo.fetchWithRetry("test123")
                .subscribe(
                        data -> System.out.println("成功: " + data),
                        err -> System.out.println("最终失败: " + err.getClass().getSimpleName() + ": " + err.getMessage())
                );

        Thread.sleep(5000);
    }
}

5. 背压(Backpressure)控制策略

背压是响应式流的核心特性,用于协调生产者和消费者的速度差异,防止下游被数据淹没 。

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Subscription;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

public class BackpressureDemo {

    // 场景1: 响应式拉取(基础背压)
    public void demonstrateRequestPull() {
        Flux.range(1, 100)
                .log() // 使用log()观察请求事件
                .subscribe(
                        data -> System.out.println("收到: " + data),
                        err -> err.printStackTrace(),
                        () -> System.out.println("完成"),
                        subscription -> {
                            subscription.request(5); // 初始请求5个
                            // 后续可根据处理能力动态调用 subscription.request(n)
                        }
                );
    }

    // 场景2: 缓冲策略 (onBackpressureBuffer)
    public Flux<Integer> handleFastProducer() {
        return Flux.interval(Duration.ofMillis(10)) // 快速生产者,每10ms一个
                .map(i -> i.intValue())
                .onBackpressureBuffer(50, // 缓冲区大小
                        dropped -> System.out.println("元素被丢弃: " + dropped), // 缓冲区满时的回调
                        BufferOverflowStrategy.DROP_LATEST) // 策略:丢弃最新的
                .doOnNext(i -> {
                    try {
                        Thread.sleep(100); // 慢速消费者,每100ms处理一个
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    System.out.println("处理: " + i);
                });
    }

    // 场景3: 丢弃策略 (onBackpressureDrop)
    public Flux<Long> handleDropStrategy() {
        return Flux.interval(Duration.ofMillis(1))
                .onBackpressureDrop(dropped -> 
                    System.out.println("下游忙,丢弃: " + dropped)
                )
                .doOnNext(i -> {
                    // 模拟不稳定的处理速度
                    try {
                        Thread.sleep((long) (Math.random() * 10));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
    }

    // 场景4: 最新值策略 (onBackpressureLatest)
    public Flux<Long> handleLatestStrategy() {
        return Flux.interval(Duration.ofMillis(1))
                .onBackpressureLatest() // 只保留最新的元素供下游消费
                .doOnNext(i -> {
                    try {
                        Thread.sleep(100); // 慢消费者
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    System.out.println("处理最新值: " + i);
                });
    }

    // 场景5: 使用 `limitRate` 进行预取优化
    public Flux<Integer> optimizeWithLimitRate() {
        return Flux.range(1, 1000)
                .log("before-limit")
                .limitRate(10) // 预取10个,处理75%后(即7个)再请求10个
                .log("after-limit")
                .doOnNext(i -> {
                    try {
                        Thread.sleep(10); // 模拟处理
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
    }

    public static void main(String[] args) throws InterruptedException {
        BackpressureDemo demo = new BackpressureDemo();
        System.out.println("=== 演示缓冲策略 ===");
        CountDownLatch latch = new CountDownLatch(1);
        demo.handleFastProducer().take(10).subscribe(
                i -> {},
                err -> err.printStackTrace(),
                latch::countDown
        );
        latch.await();
    }
}

6. 线程调度(Schedulers)最佳实践

正确的线程调度是避免阻塞、提升性能的关键。subscribeOn 指定上游执行线程,publishOn 影响下游执行线程 。

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;

public class SchedulerDemo {

    // 场景1: subscribeOn - 指定源头执行线程
    public Mono<String> fetchDataWithBlockingCall() {
        return Mono.fromCallable(() -> {
                    // 模拟阻塞IO操作(如JDBC、文件读取)
                    Thread.sleep(1000);
                    return "阻塞IO数据";
                })
                .subscribeOn(Schedulers.boundedElastic()) // 将阻塞操作移交到弹性线程池
                .doOnSubscribe(s -> System.out.println("订阅发生在线程: " + Thread.currentThread().getName()));
    }

    // 场景2: publishOn切换下游操作线程
    public Flux<Integer> processWithThreadSwitch() {
        return Flux.range(1, 5)
                .doOnNext(i -> System.out.println("生成 " + i + " 在线程: " + Thread.currentThread().getName()))
                .publishOn(Schedulers.parallel()) // 切换下游到并行线程池
                .map(i -> {
                    System.out.println("映射 " + i + " 在线程: " + Thread.currentThread().getName());
                    return i * 2;
                })
                .publishOn(Schedulers.single()) // 再次切换到单一线程
                .doOnNext(i -> System.out.println("最终处理 " + i + " 在线程: " + Thread.currentThread().getName()));
    }

    // 场景3: 并行处理 (parallel + runOn)
    public Flux<Integer> parallelComputation(Flux<Integer> heavyTasks) {
        return heavyTasks
                .parallel(4) // 将流并行化为4个轨道
                .runOn(Schedulers.parallel()) // 在每个轨道上使用并行调度器
                .map(i -> computeHeavy(i)) // 并行执行计算密集型任务
                .sequential(); // 将并行流合并回顺序流
    }

    // 场景4: 超时与调度器
    public Mono<String> callWithTimeout() {
        return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(2000); // 模拟长时间任务
                        return "结果";
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }))
                .timeout(Duration.ofSeconds(1), 
                        Mono.defer(() -> Mono.just("超时降级"))
                                .subscribeOn(Schedulers.boundedElastic())
                )
                .subscribeOn(Schedulers.boundedElastic());
    }

    private int computeHeavy(int input) {
        try {
            Thread.sleep(100); // 模拟计算
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return input * input;
    }

    public static void main(String[] args) throws InterruptedException {
        SchedulerDemo demo = new SchedulerDemo();

        System.out.println("=== publishOn 线程切换演示 ===");
        demo.processWithThreadSwitch().subscribe();

        Thread.sleep(100);

        System.out.println("
=== 并行计算演示 ===");
        demo.parallelComputation(Flux.range(1, 8))
                .subscribe(i -> System.out.println("结果: " + i));

        Thread.sleep(2000);
    }
}

7. 组合操作符(zip, merge, concat

用于将多个流组合成一个流,是处理多源数据的利器 。

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;

public class CombiningOperators {

    // 场景1: zip - 一对一组合(像拉链)
    public Flux<String> zipUserAndProfile(Flux<Long> userIds, Flux<String> userNames) {
        return Flux.zip(userIds, userNames)
                .map(tuple -> "ID: " + tuple.getT1() + ", Name: " + tuple.getT2());
        // 只有当两个流都有元素时才会组合发射
    }

    // 场景2: combineLatest - 任一更新即组合最新值
    public Flux<String> combineLatestInput(Flux<String> searchBox, Flux<String> category) {
        return Flux.combineLatest(searchBox, category, (s, c) -> "搜索: '" + s + "' 在分类: '" + c + "'");
        // 常用于实时仪表盘,任一数据源更新都重新计算视图
    }

    // 场景3: merge合并多个流,按实际发射时间交错
    public Flux<String> mergeNewsFeeds(Flux<String> feed1, Flux<String> feed2) {
        return Flux.merge(feed1, feed2).log("merge");
        // 结果顺序取决于元素实际到达时间
    }

    // 场景4: concat顺序连接流,先消费完第一个再消费第二个
    public Flux<String> concatSequentialOperations(Mono<String> op1, Mono<String> op2) {
        return Flux.concat(op1, op2); // 严格保证 op1 在 op2 之前
    }

    // 场景5: flatMap + merge 实现并行合并
    public Flux<String> fetchMultipleInParallel(List<String> urls) {
        return Flux.fromIterable(urls)
                .flatMap(url -> fetchUrl(url)) // 异步获取每个URL
                .map(response -> "Data: " + response); // 合并结果
    }

    // 场景6: zip 等待所有异步操作完成
    public Mono<AggregatedResult> aggregateUserData(Long userId) {
        Mono<User> user = fetchUser(userId);
        Mono<List<Order>> orders = fetchOrders(userId);
        Mono<Profile> profile = fetchProfile(userId);

        return Mono.zip(user, orders, profile)
                .map(tuple -> new AggregatedResult(tuple.getT1(), tuple.getT2(), tuple.getT3()));
        // 等待所有三个Mono完成,然后组合结果
    }

    private Mono<String> fetchUrl(String url) {
        return Mono.just("Response from " + url)
                .delayElement(Duration.ofMillis((long) (Math.random() * 500)));
    }

    private Mono<User> fetchUser(Long id) { return Mono.just(new User(id, "User" + id)); }
    private Mono<List<Order>> fetchOrders(Long id) { return Mono.just(List.of(new Order(id))); }
    private Mono<Profile> fetchProfile(Long id) { return Mono.just(new Profile(id)); }

    static class User { /* 省略 */ }
    static class Order { /* 省略 */ }
    static class Profile { /* 省略 */ }
    static class AggregatedResult { /* 省略 */ }

    public static void main(String[] args) throws InterruptedException {
        CombiningOperators demo = new CombiningOperators();

        System.out.println("=== zip 演示 ===");
        Flux<Long> ids = Flux.just(1L, 2L, 3L).delayElements(Duration.ofMillis(100));
        Flux<String> names = Flux.just("Alice", "Bob", "Charlie").delayElements(Duration.ofMillis(150));
        demo.zipUserAndProfile(ids, names).subscribe(System.out::println);

        Thread.sleep(1000);
    }
}

8. 综合实战:优化后的 WebFlux REST API 端点

结合上述操作符,构建健壮、高效的响应式API 。

java 复制代码
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.retry.Retry;
import java.time.Duration;
import java.util.List;

@RestController
@RequestMapping("/api/v1")
public class OptimizedReactiveController {

    private final ProductReactiveRepository productRepository;
    private final WebClient externalServiceClient;

    // API 1: 获取产品详情(并行获取库存与评价)
    @GetMapping("/products/{id}/enhanced")
    public Mono<ProductDetailResponse> getProductDetail(@PathVariable String id) {
        return productRepository.findById(id)
                .switchIfEmpty(Mono.error(new ProductNotFoundException(id)))
                .flatMap(product ->
                        Mono.zip(
                                getStockLevel(product.getSku()).subscribeOn(Schedulers.boundedElastic()),
                                getExternalRating(product.getSku()).subscribeOn(Schedulers.boundedElastic()),
                                (stock, rating) -> buildDetailResponse(product, stock, rating)
                        )
                )
                .timeout(Duration.ofSeconds(3), fallbackDetail(id))
                .retryWhen(Retry.backoff(2, Duration.ofMillis(500)))
                .onErrorResume(ProductNotFoundException.class, e -> Mono.just(ProductDetailResponse.notFound(id)))
                .onErrorResume(e -> {
                    log.error("获取产品详情失败,ID: {}", id, e);
                    return Mono.just(ProductDetailResponse.error(id));
                });
    }

    // API 2: 产品搜索(带防抖、缓存、分页)
    @GetMapping("/products/search")
    public Flux<Product> searchProducts(
            @RequestParam String q,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {

        return Mono.just(q)
                .filter(term -> term.length() >= 2)
                .switchIfEmpty(Mono.error(new IllegalArgumentException("搜索词过短")))
                .flatMapMany(term ->
                        productRepository.findByKeyword(term)
                                .filter(Product::isActive)
                                .filter(p -> p.getStock() > 0)
                                .sort((p1, p2) -> p2.getScore().compareTo(p1.getScore())) // 按相关性降序
                                .skip((long) page * size)
                                .take(size)
                                .switchIfEmpty(Flux.defer(() -> 
                                        productRepository.findFromCache(term).take(size)
                                ))
                )
                .onErrorResume(IllegalArgumentException.class, e -> Flux.empty())
                .onBackpressureBuffer(50, BufferOverflowStrategy.DROP_OLDEST);
    }

    // API 3: 批量订单创建(并行处理,限制并发,优雅降级)
    @PostMapping("/orders/batch")
    public Flux<OrderResult> createOrdersBatch(@RequestBody Flux<OrderRequest> requests) {
        return requests
                .index() // 为每个请求附加索引,便于追踪和保持顺序
                .flatMap(tuple -> {
                    Long index = tuple.getT1();
                    OrderRequest req = tuple.getT2();
                    return processSingleOrder(req)
                            .map(result -> new IndexedResult(index, result))
                            .onErrorResume(e -> 
                                    Mono.just(new IndexedResult(index, OrderResult.failed(req.getId(), e.getMessage())))
                            );
                }, 5) // 最大并发数5
                .sort((ir1, ir2) -> Long.compare(ir1.index(), ir2.index())) // 按原始请求顺序排序
                .map(IndexedResult::result);
    }

    // API 4: 服务器发送事件 (Server-Sent Events)
    @GetMapping(value = "/products/stream", produces = "text/event-stream")
    public Flux<ProductEvent> streamProductEvents(@RequestParam List<String> productIds) {
        return Flux.fromIterable(productIds)
                .flatMap(productId -> 
                        productRepository.watchProductChanges(productId) // 返回一个无限流
                                .timeout(Duration.ofMinutes(10))
                                .retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(5)))
                )
                .distinctUntilChanged(ProductEvent::getVersion) // 去重
                .sample(Duration.ofMillis(500)) // 采样,控制发射频率
                .doOnCancel(() -> log.info("客户端断开产品流"))
                .doOnError(e -> log.error("产品事件流异常", e));
    }

    // --- 辅助方法 ---
    private Mono<Integer> getStockLevel(String sku) {
        return externalServiceClient.get()
                .uri("/inventory/{sku}", sku)
                .retrieve()
                .bodyToMono(Integer.class)
                .onErrorReturn(0);
    }

    private Mono<Rating> getExternalRating(String sku) {
        return externalServiceClient.get()
                .uri("/ratings/{sku}", sku)
                .retrieve()
                .bodyToMono(Rating.class)
                .timeout(Duration.ofSeconds(2))
                .onErrorReturn(Rating.defaultRating());
    }

    private Mono<ProductDetailResponse> fallbackDetail(String id) {
        return Mono.fromCallable(() -> 
                ProductDetailResponse.fallback(id)
        ).subscribeOn(Schedulers.boundedElastic());
    }

    private Mono<OrderResult> processSingleOrder(OrderRequest req) {
        // 验证 -> 扣库存 -> 支付 -> 持久化 的异步链
        return validateRequest(req)
                .flatMap(validated -> reserveStock(validated))
                .flatMap(reserved -> processPayment(reserved))
                .flatMap(paid -> saveOrder(paid))
                .map(order -> OrderResult.success(order))
                .timeout(Duration.ofSeconds(30))
                .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
                        .filter(e -> e instanceof TemporaryServiceException)
                );
    }

    // 内部类与异常定义省略...
    static class ProductDetailResponse { /* ... */ }
    static class OrderResult { /* ... */ }
    static class IndexedResult { /* ... */ }
    static class ProductNotFoundException extends RuntimeException { /* ... */ }
    static class TemporaryServiceException extends RuntimeException { /* ... */ }
}

9. 响应式流测试 (StepVerifier)

使用 StepVerifier 是测试响应式流的行业标准,它能验证流中元素的顺序、数量和完成信号 。

java 复制代码
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.test.publisher.TestPublisher;
import java.time.Duration;
import java.util.List;

public class ReactiveServiceUnitTest {

    @Test
    void testMapAndFilter() {
        Flux<String> source = Flux.just("apple", "banana", "cherry", "date");

        StepVerifier.create(source
                        .map(String::toUpperCase)
                        .filter(s -> s.length() > 5)
                )
                .expectNext("BANANA", "CHERRY")
                .verifyComplete();
    }

    @Test
    void testFlatMapConcurrency() {
        Flux<Integer> ids = Flux.just(1, 2, 3);

        StepVerifier.create(ids.flatMap(id -> 
                        Mono.just(id * 10).delayElement(Duration.ofMillis(100)))
                )
                .expectNextCount(3) // 不保证顺序,但数量是3
                .verifyComplete();
    }

    @Test
    void testErrorHandlingAndRetry() {
        AtomicInteger attempt = new AtomicInteger();
        Mono<String> unreliable = Mono.fromCallable(() -> {
            if (attempt.incrementAndGet() < 3) {
                throw new RuntimeException("临时失败");
            }
            return "成功";
        });

        StepVerifier.create(unreliable.retry(2))
                .expectNext("成功")
                .verifyComplete();
    }

    @Test
    void testSwitchMapCancellation() {
        TestPublisher<String> publisher = TestPublisher.create();
        Flux<String> source = publisher.flux();

        StepVerifier.create(source.switchMap(s -> 
                        Mono.just("processed-" + s).delayElement(Duration.ofMillis(200)))
                )
                .then(() -> {
                    publisher.next("first");
                    publisher.next("second"); // 第二个元素会取消第一个元素的处理
                })
                .expectNext("processed-second") // 只期望收到第二个的处理结果
                .thenCancel()
                .verify();
    }

    @Test
    void testVirtualTimeForSlowOperations() {
        StepVerifier.withVirtualTime(() -> 
                        Flux.interval(Duration.ofSeconds(1)).take(5)
                )
                .expectSubscription()
                .thenAwait(Duration.ofSeconds(5)) // 虚拟时间快进5秒
                .expectNextCount(5)
                .verifyComplete();
    }

    @Test
    void testBackpressure() {
        Flux<Integer> source = Flux.range(1, 100);

        StepVerifier.create(source.onBackpressureBuffer(10))
                .expectSubscription()
                .thenRequest(1)
                .expectNext(1)
                .thenRequest(5)
                .expectNext(2, 3, 4, 5, 6)
                .thenCancel()
                .verify();
    }
}

参考来源

相关推荐
无心水2 小时前
【OpenClaw:赚钱】案例19、内容产量5倍、广告收入翻4倍:播客转多平台内容矩阵全自动化实战(OpenAI Whisper + Claude)
java·人工智能·python·ai编程·openclaw·养龙虾·java.time
云烟成雨TD2 小时前
Spring AI 1.x 系列【42】MCP 服务端 Spring Boot 启动器
java·人工智能·spring
云烟成雨TD2 小时前
Spring AI 1.x 系列【38】模型上下文协议(MCP)
java·人工智能·spring
Alson_Code2 小时前
Spring AI-1.1.0
java·人工智能·后端·spring·ai编程
ANnianStriver2 小时前
PetLumina 08 — 通知系统与搜索功能修复(广播机制 + 已读状态 + 参数对齐)
java·ai·ai编程·广播机制
ggaofeng2 小时前
试用zeroclaw
java·开发语言
就叫_这个吧2 小时前
servlet整合tomcat项目启动报错解决,org.apache.tomcat.util.descriptor.web.WebXml.setVersion
java·servlet·tomcat·apache
Wenzar_2 小时前
用 JAX 构建可微分光子神经网络仿真器
java·人工智能·深度学习·神经网络
cfm_29143 小时前
RocksDB 初步了解
java