Reactor几个重要的接口(1)

Reactor ContextView 详解:响应式上下文的只读视图

在 Reactor 响应式编程中,Context 是用于在操作符链中传递元数据(如追踪 ID、用户认证信息)的核心机制。ContextView 是 Reactor 3.4.0 引入的只读接口,它将上下文的读取能力写入能力 分离,使 API 更加清晰且安全。本文将逐段解析 ContextView 接口的源码,说明其设计意图、各方法的作用以及它与 Context 的关系。


一、接口概览与设计目标

java 复制代码
package reactor.util.context;

import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

import org.jspecify.annotations.Nullable;

/**
 * A read-only view of a collection of key/value pairs that is propagated between components
 * such as operators via the context protocol. Contexts are ideal to transport orthogonal
 * information such as tracing or security tokens.
 * <p>
 * {@link Context} is an immutable variant of the same key/value pairs structure which exposes
 * a write API that returns new instances on each write.
 */
public interface ContextView {
    // ... 方法列表
}

核心设计理念

  • 只读分离ContextView 只提供读取方法,不提供写入。写入操作由子接口 Context 负责,并返回新实例(不可变)。
  • 不可变性:所有实现都是不可变的,保证线程安全。
  • 正交信息传递:用于传播不干扰主要业务逻辑的额外数据,如链路追踪、安全令牌等。
  • 类型安全 :支持按 Class 类型获取值,避免强制转换。

二、方法逐个解析

1. <T> T get(Object key)

java 复制代码
<T> T get(Object key);

作用 :根据给定的键获取对应的值。如果键不存在,抛出 NoSuchElementException

使用场景:当确信键一定存在时,直接获取;否则使用带默认值的方法。

返回值:泛型方法,允许调用者直接指定期望的类型(隐式转换)。

注意 :调用者需自行保证类型安全,因为实际存储的值类型在编译时未知。若类型不匹配,会出现 ClassCastException


2. default <T> T get(Class<T> key)

java 复制代码
default <T> T get(Class<T> key) {
    T v = get((Object) key);
    if (key.isInstance(v)) {
        return v;
    }
    throw new NoSuchElementException("Context does not contain a value of type " + key.getName());
}

作用 :使用 Class 对象作为键,并自动检查值类型是否匹配。若键不存在或值类型不匹配,抛出 NoSuchElementException

设计意图:允许以类型作为键,避免字符串硬编码,且提供类型安全保证。

实现细节

  • 先调用 get(Object key) 获取原始值。
  • 通过 key.isInstance(v) 检查值是否属于该类型。
  • 若匹配则返回,否则抛异常。

3. default <T> T getOrDefault(Object key, T defaultValue)

java 复制代码
default <T> @Nullable T getOrDefault(Object key, @Nullable T defaultValue) {
    if (!hasKey(key)) {
        return defaultValue;
    }
    return get(key);
}

作用:若键存在则返回对应的值,否则返回指定的默认值。

便利性 :避免重复检查 hasKey 并处理 Optional


4. default <T> Optional<T> getOrEmpty(Object key)

java 复制代码
default <T> Optional<T> getOrEmpty(Object key) {
    if (hasKey(key)) {
        return Optional.of(get(key));
    }
    return Optional.empty();
}

作用 :将值包装在 Optional 中,若键不存在则返回 Optional.empty()

风格选择:适合函数式编程风格的调用者,便于链式处理。


5. boolean hasKey(Object key)

java 复制代码
boolean hasKey(Object key);

作用:检查上下文中是否包含指定的键。

实现 :由具体子类实现,通常通过内部存储结构(如 Map)的 containsKey 判断。


6. default boolean isEmpty()

java 复制代码
default boolean isEmpty() {
    return size() == 0;
}

作用:判断上下文是否为空(不包含任何键值对)。

默认实现 :依赖 size() 方法,若大小为 0 则返回 true


7. int size()

java 复制代码
int size();

作用:返回上下文中键值对的数量。

实现 :由子类提供,通常为内部存储结构的 size()


8. Stream<Map.Entry<Object, Object>> stream()

java 复制代码
Stream<Map.Entry<Object, Object>> stream();

作用 :以 Stream 的形式返回所有键值对的视图,便于遍历、过滤、聚合等操作。

用途

  • 调试时打印所有内容。
  • 批量检查或转换。

注意 :返回的是不可变条目(如 SimpleImmutableEntry),防止修改。


9. default void forEach(BiConsumer<Object, Object> action)

java 复制代码
default void forEach(BiConsumer<Object, Object> action) {
    stream().forEach(entry -> action.accept(entry.getKey(), entry.getValue()));
}

作用:遍历所有键值对,并对每个条目执行给定的操作。

默认实现 :基于 stream()forEach 构建,简单直观。

优势 :与 Java 8 的 Map.forEach 风格一致,便于迁移。


三、ContextViewContext 的关系

  • Context 接口继承 ContextView :增加了写入方法 putdelete 等,且这些方法返回新的 Context 实例(不可变)。
  • 读写分离 :在方法签名中,若只需读取上下文,应使用 ContextView 作为参数类型,表明不修改上下文。
  • 实例类型 :实际运行时,ContextView 的实例都是 Context 的具体实现类(如 Context1ContextN),但可以向上转型为只读视图。

示例

java 复制代码
// 在自定义操作符中,只读上下文
public void doSomething(ContextView ctx) {
    String traceId = ctx.getOrEmpty("traceId").orElse("default");
}

四、在 Reactor 流中的使用

  • CoreSubscriber.currentContext() 返回 ContextView,但实际实现为 Context
  • 操作符 transformDeferredContextual 接收 BiFunction,其中第二个参数是 ContextView
  • Mono.deferContextualFlux.deferContextual 允许订阅时访问上下文。

五、设计优势总结

  1. 清晰性:将只读与读写分离,使 API 意图明确。
  2. 安全性:不可变性确保多线程环境下无并发修改问题。
  3. 类型安全:提供按类型获取值的方法,减少强制转换错误。
  4. 灵活性:提供多种获取方式(直接、默认值、Optional、Stream),适应不同编程风格。
  5. 可组合性stream()forEach 支持批量操作和函数式处理。

六、注意事项

  • 键值不能为 nullContext 的实现强制键和值非 null,因此 get 不会返回 null
  • 性能考虑ContextView 的实现(如 ContextN)内部使用 HashMap,查询效率 O(1)。特化类(Context1~Context5)更轻量。
  • ThreadLocal 的区别ContextView 绑定在订阅上,随数据流传递,而非线程。

七、代码片段示例

java 复制代码
// 创建上下文
Context ctx = Context.of("userId", 123, "traceId", "abc-123");

// 只读视图
ContextView view = ctx;

// 获取值
int userId = view.get("userId");                   // 123
String trace = view.getOrDefault("traceId", "default");
Optional<String> maybeTrace = view.getOrEmpty("traceId");

// 遍历
view.forEach((k, v) -> System.out.println(k + "=" + v));

// 转换为 Stream
view.stream().forEach(System.out::println);

通过上述分析,可以看出 ContextView 是 Reactor 响应式编程中传递元数据的标准化只读接口,其设计兼顾了安全、性能与易用性。理解它有助于更好地利用上下文传递功能,编写更健壮的响应式组件。

Reactor SynchronousSink 接口详解:同步生成器的核心契约

SynchronousSink<T> 是 Project Reactor 中用于同步、逐项生成数据 的核心接口。它通常与 Flux.generateMono.create 配合使用,允许开发者在每次被请求时同步地发出一个、多个或结束信号。理解该接口对于自定义响应式数据源至关重要。


一、接口概述

java 复制代码
public interface SynchronousSink<T> {
    void complete();
    @Deprecated Context currentContext();
    default ContextView contextView() { return currentContext(); }
    void error(Throwable e);
    void next(T t);
}
  • 角色 :作为生成器函数(Function / Consumer)与下游订阅者之间的桥梁,提供信号发送(nextcompleteerror)和上下文访问能力。
  • 同步限制 :所有方法必须在调用生成器的线程上同步执行 ,不能用于异步回调(此时应使用 FluxSinkMonoSink)。
  • 单次信号 :在一次 generate 调用中,next 最多调用一次,且随后可调用一次 completeerror,但不可同时调用多个终端信号。

二、方法逐行解析

1. void complete()

java 复制代码
/**
 * @see Subscriber#onComplete()
 */
void complete();

作用 :发出完成信号 ,表示数据流结束。下游订阅者将收到 onComplete() 回调。

约束

  • 调用后该 SynchronousSink 实例不应再被使用。
  • 若同时调用 nextcomplete,行为取决于实现(通常允许先 nextcomplete,但不可颠倒)。
  • error 互斥,只能调用其中一种终端信号。

典型用法 :在 generate 中,当状态不再产生新元素时调用 complete() 终止流。


2. @Deprecated Context currentContext()

java 复制代码
/**
 * @deprecated To be removed in 3.6.0 at the earliest. Prefer using #contextView() instead.
 */
@Deprecated
Context currentContext();

作用 :返回当前订阅者的完整 Context 对象(可读可写,虽然实际不应写入)。

弃用原因

  • 返回 Context 接口(可写),但 SynchronousSink 只应读取上下文,不应修改。
  • 为了贯彻读写分离原则,Reactor 3.4.0 引入了 ContextView(只读),并在 3.5.0 后弃用本方法,推荐使用 contextView()

替代方案 :使用新的默认方法 contextView()


3. default ContextView contextView()

java 复制代码
/**
 * Return the current subscriber's context as a {@link ContextView} for inspection.
 */
default ContextView contextView() {
    return currentContext();
}

作用 :返回当前订阅者上下文的只读视图ContextView)。

设计意图

  • 提供类型安全的只读访问,防止意外修改。
  • currentContext() 的替代品,未来将取代后者。

实现 :默认委托给已弃用的 currentContext(),但具体实现类可直接覆写以返回 ContextView 实例。

使用场景 :在生成器函数中读取下游通过 contextWrite 注入的元数据(如 userIdtraceId)。


4. void error(Throwable e)

java 复制代码
/**
 * @param e the exception to signal, not null
 * @see Subscriber#onError(Throwable)
 */
void error(Throwable e);

作用 :发出错误信号 ,终止流并将异常传递给下游 onError

约束

  • e 不能为 null
  • complete 互斥,只能调用其中一种。
  • 调用后不能再发送任何信号。

典型用法:在生成过程中发生不可恢复的错误时,终止流并传递异常。


5. void next(T t)

java 复制代码
/**
 * Try emitting, might throw an unchecked exception.
 *
 * @param t the value to emit, not null
 * @throws RuntimeException in case of unchecked error during the emission
 * @see Subscriber#onNext(Object)
 */
void next(T t);

作用 :发出一个数据元素给下游。

约束

  • t 不能为 null(Reactive Streams 规范禁止 null 元素)。
  • 在一次 generate 调用中,next 最多调用一次 (这是与 FluxSink 的主要区别)。
  • 调用后可选地调用 completeerror 结束流。

异常 :若底层订阅者取消或发生错误,调用可能抛出 RuntimeException


三、与 Context / ContextView 的关系

关系图解

复制代码
┌─────────────────┐          ┌─────────────────────┐
│   ContextView    │<────────│  SynchronousSink    │
│  (只读接口)      │          │  (上下文访问者)      │
└────────┬────────┘          └─────────────────────┘
         │
         │ extends
         ▼
┌─────────────────┐
│    Context      │
│  (可写接口)      │
└─────────────────┘
  • ContextView :定义只读操作(gethasKeystream 等),是 SynchronousSink 所依赖的上下文访问接口。
  • Context :继承 ContextView,并增加写入方法(putdelete 等),返回新实例。
  • SynchronousSink :通过 contextView() 获取当前订阅者的 ContextView,从而读取上下文信息;其 currentContext()(已弃用)返回 Context,但不应使用写入功能。

为什么不用 Context 而用 ContextView

  • 安全:生成器函数应只读上下文,避免意外修改导致线程安全问题或副作用。
  • 清晰:明确表明该接口不提供写入能力,符合单一职责原则。

generate 中的典型用法

java 复制代码
Flux.generate(
    () -> 0,
    (state, sink) -> {
        ContextView ctx = sink.contextView();
        String traceId = ctx.getOrDefault("traceId", "default");
        // 使用 traceId 记录日志或调整行为
        if (state < 10) {
            sink.next(state);
            return state + 1;
        } else {
            sink.complete();
            return state;
        }
    }
);

四、设计考量与注意事项

  1. 同步性SynchronousSink 的方法必须在生成器的线程上同步调用,不能存储引用后在异步回调中使用。
  2. 一次 next :与 FluxSink(允许多次 next)不同,SynchronousSink 每次生成器调用最多发出一个元素,适用于有状态、逐项生成的场景(如 generate)。
  3. 非空约束 :所有信号(next 的值、error 的异常)均不允许为 null
  4. 上下文读取时机contextView() 在订阅时可用,反映下游订阅者通过 contextWrite 注入的最新上下文。

五、总结

  • SynchronousSink 是同步生成器的核心信号发送接口,提供 nextcompleteerror 三种信号,以及只读的上下文访问。
  • 与上下文的关系 :通过 contextView()(或已弃用的 currentContext())暴露当前订阅者的 ContextView,使生成器能感知下游元数据。
  • 设计进化currentContext() 弃用并替换为 contextView(),体现了 Reactor 对读写分离和类型安全的持续改进。

掌握 SynchronousSink 是熟练运用 Flux.generate 进行自定义同步数据生成的基础,也是理解 Reactor 数据流控制流程的重要一环。

Reactor Fuseable 接口详解:响应式流融合优化的核心 API

Fuseable 是 Project Reactor 中用于**流融合(Stream Fusion)**优化的关键接口。它允许数据流在操作符链中进行深度优化,减少内存分配和请求/响应开销,提升吞吐量。本文将逐段解析 Fuseable 接口的完整源码,说明其设计意图、各内部接口的作用,以及与先前讨论的 ContextSynchronousSink 等组件的关系。


一、接口概述与设计背景

java 复制代码
package reactor.core;

// ... imports

/**
 * A micro API for stream fusion, in particular marks producers that support a {@link QueueSubscription}.
 */
public interface Fuseable {
    // 常量定义
    int NONE = 0;
    int SYNC = 1;
    int ASYNC = 2;
    int ANY = 3;
    int THREAD_BARRIER = 0b100; //4

    // 静态辅助方法
    static String fusionModeName(int mode) { ... }
    static String fusionModeName(int mode, boolean ignoreThreadBarrier) { ... }

    // 内部接口
    interface ConditionalSubscriber<T> extends CoreSubscriber<T> { ... }
    interface QueueSubscription<T> extends Queue<T>, Subscription { ... }
    interface SynchronousSubscription<T> extends QueueSubscription<T> { ... }
    interface ScalarCallable<T> extends Callable<T> { }
}

设计目标

  • 减少操作符之间的开销,例如将多个操作符合并为单个队列操作(poll),避免每次元素传递都涉及订阅请求。
  • 区分同步融合(SYNC)和异步融合(ASYNC),以及表示"不支持融合"的 NONE
  • 提供标记接口,使发布者和订阅者能够协商是否以及如何融合。

二、常量定义

java 复制代码
int NONE = 0;           // 不支持任何融合模式
int SYNC = 1;           // 支持同步融合(拉取模式,无背压请求)
int ASYNC = 2;          // 支持异步融合(拉取模式,但仍需背压管理)
int ANY = 3;            // 请求者不指定模式,由生产者决定
int THREAD_BARRIER = 4; // 标记位,指示数据可能跨线程提取,需注意线程安全
  • SYNCASYNC :同步融合适用于所有元素已在内存中的源(如 Flux.just),可通过 poll() 直接拉取,无需 request 调用。异步融合适用于非阻塞异步源(如 Flux.range 虽同步但也可),仍需处理背压但可复用队列。
  • ANY :当操作符不关心具体模式时,请求 ANY,让生产者返回其最佳支持的模式。
  • THREAD_BARRIER :标记位,与模式组合(如 SYNC | THREAD_BARRIER),表示 poll() 可能被另一个线程调用(例如在 publishOn 后),消费者需注意线程安全性。

三、静态方法 fusionModeName

java 复制代码
static String fusionModeName(int mode) {
    return fusionModeName(mode, false);
}

static String fusionModeName(int mode, boolean ignoreThreadBarrier) {
    int evaluated = mode;
    String threadBarrierSuffix = "";
    if (mode >= 0) {
        evaluated = mode & ~THREAD_BARRIER; // 移除标志位
        if (!ignoreThreadBarrier && (mode & THREAD_BARRIER) == THREAD_BARRIER) {
            threadBarrierSuffix = "+THREAD_BARRIER";
        }
    }

    switch (evaluated) {
        case -1: return "Disabled"; // 特殊值,表示融合完全禁用
        case Fuseable.NONE: return "NONE" + threadBarrierSuffix;
        case Fuseable.SYNC: return "SYNC" + threadBarrierSuffix;
        case Fuseable.ASYNC: return "ASYNC" + threadBarrierSuffix;
        default: return "Unknown(" + evaluated + ")" + threadBarrierSuffix;
    }
}
  • 作用:将融合模式整数值转换为人类可读的字符串,便于调试和日志输出。
  • 逻辑
    1. 如果 mode >= 0,先清除 THREAD_BARRIER 位(通过 & ~THREAD_BARRIER),并检查是否设置了该标志,若设置且 ignoreThreadBarrierfalse,则追加 "+THREAD_BARRIER" 后缀。
    2. 根据清除后的 evaluated 值匹配常量,返回对应名称;若为 -1(特殊禁用标志)返回 "Disabled";其他未知值返回 "Unknown(x)"
  • 设计注意:该方法仅用于人类可读输出,不应用于逻辑判断,因为命名可能变化。

四、内部接口 ConditionalSubscriber

java 复制代码
interface ConditionalSubscriber<T> extends CoreSubscriber<T> {
    /**
     * Try consuming the value and return true if successful.
     * @param t the value to consume, not null
     * @return true if consumed, false if dropped and a new value can be immediately sent
     */
    boolean tryOnNext(T t);
}
  • 作用 :扩展 CoreSubscriber,增加 tryOnNext 方法,允许订阅者尝试消费一个元素,并返回是否成功。
  • 优化点 :当元素被过滤或丢弃时,返回 false,上游可以立即尝试发送下一个元素,而无需等待新的 request(1) 请求,减少往返开销。
  • 典型场景 :在 filter 操作符中,若谓词不匹配,订阅者可返回 false,上游直接发送下一个元素,无需额外请求。

五、核心接口 QueueSubscription<T>

java 复制代码
interface QueueSubscription<T> extends Queue<T>, Subscription {
    // 独有方法
    int requestFusion(int requestedMode);

    // 覆盖 Queue 的大部分方法,抛出 UnsupportedOperationException
    @Override default T peek() { throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); }
    @Override default boolean add(@Nullable T t) { throw ... }
    @Override default boolean offer(@Nullable T t) { throw ... }
    @Override default T remove() { throw ... }
    @Override default T element() { throw ... }
    @Override default boolean contains(@Nullable Object o) { throw ... }
    @Override default Iterator<T> iterator() { throw ... }
    @Override default Object[] toArray() { throw ... }
    @Override default <T1> T1[] toArray(T1[] a) { throw ... }
    @Override default boolean remove(@Nullable Object o) { throw ... }
    @Override default boolean containsAll(Collection<?> c) { throw ... }
    @Override default boolean addAll(Collection<? extends T> c) { throw ... }
    @Override default boolean removeAll(Collection<?> c) { throw ... }
    @Override default boolean retainAll(Collection<?> c) { throw ... }
}
  • 作用 :同时作为 Queue(数据队列)和 Subscription(背压控制),允许下游通过 poll() 拉取数据,而无需每次 requestonNext 的配对。
  • 关键方法 requestFusion(int requestedMode)
    • 由操作符调用,传入其期望的融合模式(SYNCASYNCANY)。
    • 生产者根据自身能力返回实际支持的模式(SYNCASYNCNONE)。
    • 如果返回 SYNC,则下游可假定所有数据可通过 poll() 直接拉取完成,无需调用 request
    • 如果返回 ASYNC,则仍需要调用 request 来触发数据生产,但数据通过 poll() 拉取,节省了 onNext 回调开销。
  • Queue 方法的默认实现 :抛出 UnsupportedOperationException,因为 QueueSubscription 仅要求实现 pollclearsizeisEmpty 等基本队列方法,其他方法(如 addremove)在流上下文中无意义,默认禁用。
  • NOT_SUPPORTED_MESSAGE :明确说明 QueueSubscription 是内部用途,不应当作普通 Queue 使用。

六、SynchronousSubscription 标记接口

java 复制代码
interface SynchronousSubscription<T> extends QueueSubscription<T> {
    @Override
    default int requestFusion(int requestedMode) {
        if ((requestedMode & Fuseable.SYNC) != 0) {
            return Fuseable.SYNC;
        }
        return NONE;
    }
}
  • 作用 :表示该订阅器支持同步融合(所有数据立即可用)。
  • requestFusion 默认实现 :如果请求的模式包含 SYNC(即 requestedMode & SYNC != 0),则返回 SYNC;否则返回 NONE
  • 简化实现 :实现此接口的类只需确保 poll() 可以阻塞或不阻塞地返回数据,不需要额外编写 requestFusion 逻辑。

七、ScalarCallable<T> 标记接口

java 复制代码
interface ScalarCallable<T> extends Callable<T> { }
  • 作用 :标记一个发布者能够同步返回一个标量值(或 null),常用于 Mono.just 等场景。
  • 优化 :在编译时或运行时,框架可以直接调用 call() 获取值,避免构建完整的响应式链。
  • Fuseable 关系:独立标记接口,常与融合机制结合,但本身不提供融合逻辑。

八、与之前讲解组件的关系

组件 关系
Context / ContextView 正交关系。Fuseable 关注性能优化,Context 关注元数据传递。某些融合操作符可能需要读取 Context,但 Fuseable 本身不依赖它们。
SynchronousSink 使用场景不同。SynchronousSink 用于 Flux.generate 的同步生成,而 Fuseable 用于操作符之间的优化。两者可能在 generate 融合时协同工作,但无直接依赖。
CoreSubscriber ConditionalSubscriber 扩展了 CoreSubscriber,因此融合机制建立在订阅者扩展上。
SubscriptionQueue QueueSubscription 同时实现两者,将数据拉取与背压控制统一。

九、融合协商流程示例

  1. 下游操作符(如 map)通过 requestFusion(ANY) 向上游请求融合。
  2. 上游(如 Flux.just)返回 SYNC,表示支持同步融合。
  3. 下游将上游的 QueueSubscription 视为队列,通过 poll() 逐个获取元素,无需调用 requestonNext
  4. 这样整个链中的多个操作符可能合并为一次 poll 循环,大幅减少方法调用开销。

十、总结

  • Fuseable 是 Reactor 内部性能优化的核心 API,通过融合减少请求-回调开销。
  • 关键内部接口
    • ConditionalSubscriber:允许尝试消费并返回结果,优化过滤场景。
    • QueueSubscription:将队列与订阅结合,支持拉取模式。
    • SynchronousSubscription:简化同步源的融合协商。
    • ScalarCallable:标记标量源,便于快速获取值。
  • 与先前组件关系Fuseable 主要作用于发布者和订阅者的实现层,与上下文传递(ContextView)和同步生成器(SynchronousSink)领域正交,但共同构成 Reactor 的完整功能体系。

掌握 Fuseable 有助于理解 Reactor 内部的高性能原理,并能在自定义操作符中充分利用融合优化。

相关推荐
LUCKY-LIVING1 小时前
ELF File in linux
android·java·linux
星核0penstarry2 小时前
DeepSeek-V4-Flash 正式公测:大模型行业进入「极速平价普惠时代」
java·开发语言·人工智能
xieliyu.2 小时前
Java 线程池入门详解:线程池基础定义 + ThreadPoolExecutor 全参构造方法逐参数解析
java·开发语言·idea·javaee
老白干4 小时前
jjwt 0.9.1 在 JDK 11+ 上的两个“坑”与完整解决方案
java·python·log4j
圆山猫11 小时前
[Virtualization](四):Linux KVM/RISC-V 的 vCPU 运行路径
java·linux·risc-v
城管不管11 小时前
ReAct、Plan-and-Execute、Reflection 三大智能 Agent 范式核心区别
java·人工智能·算法·spring·ai·动态规划
IT小白杨12 小时前
从环境制备到自动化工作流:多账号运营的工程化架构拆解
java·经验分享·自动化·安全架构·指纹浏览器
豆瓣鸡12 小时前
算法日记 - Day3
java·开发语言·算法
萧瑟余晖12 小时前
Java深入解析篇九之NIO详解
java·网络·nio