Reactive mono how to propagate subscription context to `doOnSubscribe` and `doFinally`

Viewed 1213

I have following aspect that tracks method execution time:

public Object addMetricsToReactiveMonoTimedMethod(ProceedingJoinPoint pjp, ReactiveTimed reactiveTimed) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        Mono<?> mono = (Mono<?>) pjp.proceed();
        return mono
                .doOnSubscribe(subscription -> stopWatch.start())
                .doFinally(signalType -> {
                    stopWatch.stop();
                    logTimer(pjp, stopWatch, reactiveTimed.name(), signalType);
                });
}

Method itself looks like that:

public Mono<String> sayHi() { 
  return Mono.just("hi")
    .subscriberContext(context -> context.put("requestId", "requestId"));
}

How can I get requestId variable from subscriber context in my aspect method? I want to use it in doFinally to know which request was profiled.

1 Answers

Disclaimer: I have never used Reactor or anything like it in my whole life. I found this question due to the aspectj tag.

After a quick look at the Mono Javadoc to me it looks like you could just call subscriberContext(Function<Context, Context>) again in the aspect, just like in the target method. You get the existing context as an input parameter for the function or lambda and can do with it what you want. The result of your dummy function/lambda would be a new context, but you can just discard it. I have not tested it, but I mean something like this:

// ...

Mono<?> mono = (Mono<?>) pjp.proceed();

// Alternatively, use a List<Context> with one element, a Stack<Context>, ...
Context[] targetContext = new Context[1];

mono.subscriberContext(context -> {
  targetContext[0] = context;
  // We can also return null, it does not matter because we are not interested
  // in the newly created context, only in the original one we salvaged into the
  // outer array.
  return context;
});

System.out.println("Now do whatever you need to do with " + targetContext[0]);

// ...

I use a single-element array/list - instead you could "abuse" any other wrapper object such as an atomic reference or a thread-local as a wrapper because you cannot directly assign a to a Context variable from inside the lambda. The code would not compile because the outer variable referenced from inside the lambda needs to be effectively final.

Related