【问题标题】:Project Reactor - the usage of defer() to make method retry-ableProject Reactor - 使用 defer() 使方法可重试
【发布时间】:2026-01-13 18:05:01
【问题描述】:

最近我尝试通过设置模拟依赖项以在最后返回成功的 mono.just() 之前返回多个 Mono.error() 来测试单元测试中的重试行为:

@Mock
Dependency dependency;

@InjectMocks
ClassUnderTest classUnderTest;

@Test
void someTest() {
    final Object object = new Object();
    when(dependency.method(anyString()))
        .thenReturn(Mono.error(new Exception()))
        .thenReturn(Mono.error(new Exception()))
        .thenReturn(Mono.error(new Exception()))
        .thenReturn(Mono.just(object));

    StepVerifier.create(classUnderTest.method("abc"))
        .expectNext(object)
        .verifyComplete();

    verify(dependency, times(4)).method("abc");
}

上面的设置不起作用,正如我后来发现的那样,Reactor 中的重试不是通过在特定时间内调用方法来完成的,而是通过调用一次方法,获取发布者并重新订阅它一次又一次。

class ClassUnderTest {
    private Dependency dependency;

    public Mono<Object> method(final String str) {
        return this.dependency.method(str).retryWhen(Retry.max(3));
    }
}

如果Dependency#method 实现为:

,则重新订阅将不起作用
class Dependency {
    private OtherDependency otherDependency;

    public Mono<Object> method(final String str) {
        return this.otherDependency.get(str).map(/* some mapping logic */);
    }
}

Dependency#method 不能对OtherDependency#get 是否被延迟做太多假设。因此,Dependency 需要:

class Dependency {
    private OtherDependency otherDependency;

    public Mono<Object> method(final String str) {
        return Mono.defer(() -> this.otherDependency.get(str)).map(/* some mapping logic */);
    }
}

既然我们想说每个方法都应该是“可重试的”,这是否意味着我们需要始终使用defer(...)

还是我误解了什么?

【问题讨论】:

    标签: java reactive-programming project-reactor


    【解决方案1】:

    我应该考虑一下的。

    更简单的方法是在附加 retryWhen 运算符之前使用 defer 包装发布者,而不是使所有方法都“可重试”。

    之前:

    class ClassUnderTest {
        private Dependency dependency;
    
        public Mono<Object> method(final String str) {
            return dependency.method(str).retryWhen(Retry.max(3));
        }
    }
    

    之后:

    class ClassUnderTest {
        private Dependency dependency;
    
        public Mono<Object> method(final String str) {
            return Mono.defer(() -> dependency.method(str)).retryWhen(Retry.max(3));
        }
    }
    

    现在,我们不必说所有方法都应该是本机“可重试”的,而是始终用defer 包装要重试的发布者。

    【讨论】:

    • 这行得通,但是在编写自己的 Mono-returning 方法时,您应该尽可能返回一个冷发布者,即为每个订阅重新触发数据生成的发布者,例如您的延迟发布者。冷是默认的选择,热是我想说的例外。
    • 嗨@SimonBaslé!感谢您的评论。我是反应式编程的新手。在我早期阅读 Project Reactor 的参考资料时,我找不到关于热发布者与冷发布者的一般最佳实践以及何时以及何时不使用 defer()。所以我说得对吗,在调用依赖项的方法时,我应该尽可能使用defer()fromSupplier() 或其他变体?