获取返回 Mono.empty() 的 Mono/Flux 的行号

Getting the line number of the Mono/Flux that returned Mono.empty()

假设我有一长串 Monos。链中的某些单声道可能 return Mono.empty().

我可以用 switchIfEmpty 恢复,但我想知道哪个单声道引发了空(也许这样我就可以知道在哪里添加更智能的空处理)。

有没有办法以编程方式获取此信息?

愚蠢的例子。在我 return how did I get here? 的情况下,我如何知道第一个 flatMap 或第二个 flatMap 是否触发了空处理程序?

        Mono.just("data")
                .flatMap(t -> {
                    if (System.currentTimeMillis() % 2 == 0) {
                        return Mono.empty();
                    }
                    return Mono.just("happy1");
                })
                .flatMap(t -> {
                    if (System.currentTimeMillis() % 2 == 0) {
                        return Mono.empty();
                    }
                    return Mono.just("happy2");
                })
                .map(s -> {
                    return "successful complete: " + s;
                })
        .switchIfEmpty(Mono.fromCallable(() -> {
            return "how did I get here?";
        }))
        .block();

由于 FluxMono 的动态特性,以及 onComplete 信号被认为足够中性,通常只是通过,所以没有通用解决方案。

在您的特定示例中,您可以将 Mono.empty() 替换为 Mono.empty().doOnComplete(() -> /* log something */) 之类的内容。 您甚至可以直接在 if 块中执行日志记录,但装饰空技巧可能适用于更多情况。

另一种可能性是将空虚变成错误,而不是打开 onComplete 信号。 错误不那么中性,因此有一些方法可以丰富它们以用于调试目的。例如,在每个 flatMap 之后使用 .checkpoint("flatMapX") 语句,您将获得额外的堆栈跟踪部分,这些部分将指向由于空而失败的 flatMap。

Mono 中将空虚变成错误的一种方法是 .single(),它将强制执行一个 onNext() 或传播 onError(NoSuchElementException).

使用此技巧要记住的一件事是 checkpoint 的位置很重要:它必须在 single() 之后,以便从 single() 引发的错误得到检测和丰富.

因此,如果我以您的代码段为基础:

static final String PARSEABLE_MARKER = "PARSEABLE MARKER: <";
static final char MARKER_END = '>';

String parseLocation(Exception e) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    e.printStackTrace(pw);

    String trace = sw.toString();
    int start = trace.indexOf(PARSEABLE_MARKER);
    if (start > 0) {
        trace = trace.substring(start + PARSEABLE_MARKER.length());
        trace = trace.substring(0, trace.indexOf(MARKER_END));
        return trace;
    }
    return "I don't know";
}

String testInner() {
    Random random = new Random();
    final boolean first = random.nextBoolean();

    return Mono.just("data")
            .flatMap(t -> {
                if (System.currentTimeMillis() % 2 == 0 && first) {
                    return Mono.empty();
                }
                return Mono.just("happy1");
            })
            .single()
            .checkpoint(PARSEABLE_MARKER + "the first flatMap" + MARKER_END)
            .flatMap(t -> {
                if (System.currentTimeMillis() % 2 == 0 && !first) {
                    return Mono.empty();
                }
                return Mono.just("happy2");
            })
            .single()
            .checkpoint(PARSEABLE_MARKER + "the second flatMap" + MARKER_END)
            .map(s -> {
                return "successful complete: " + s;
            })
            .onErrorResume(NoSuchElementException.class, e ->
                    Mono.just("how did I get here? " + parseLocation(e)))
            .block();
}

这可以是 运行 在一个测试循环中,例如:

    @Test
void test() {
    int successCount = 0;
    int firstCount = 0;
    int secondCount = 0;
    for (int i = 0; i < 100; i++) {
        String message = testInner();
        if (message.startsWith("how")) {
            if (message.contains("first")) {
                firstCount++;
            }
            else if (message.contains("second")) {
                secondCount++;
            }
            else {
                System.out.println(message);
            }
        }
        else {
            successCount++;
        }
    }
    System.out.printf("Stats: %d successful, %d detected first, %d detected second", successCount, firstCount, secondCount);
}

打印如下内容:

Stats: 85 successful, 5 detected first, 10 detected second