Spring WebFlux (Reactor3) context processing

Related Reading

Spring WebFlux(Reactor3)重试

Spring WebFlux (Reactor3) reactive programming handles exceptions

Preface

In Spring MVC, we can use ThreadLocal to save some information as the context of the request. Some public variables do not need to be written in the method, but can be obtained through the context (for example: the user's login information). In "Refactoring" Martin Fowler also recommends not to use method parameters to pass in variables that can be obtained by other means. There will be multiple thread switching in Spring WebFlux, and ThreadLocal cannot play a role in Spring WebFlux. Unless you manually copy between ThreadLocal threads, such processing is obviously unwise. How to deal with contextual information and the transfer of variables between multiple threads in Spring WebFlux?

Reactor context processing

Spring WebFlux uses Reactor at the bottom, and there is a Context feature in Reactor to store the context information of the call chain. Before learning how Spring WebFlux handles contextual information, let's first understand how Reactor handles it.

The Context in Reactor is also a Map structure, stored in the form of key-value. Let's take a practical look at how Reactor handles context through a few examples from Reactor Guide. Since Reactor's Context has been revised in 3.4.3, Spring Boot 2.3.3 is based on Reactor 2.3, so I have posted both versions of the examples.

Example 1:

3.4.3 version

String key = "message";
Mono<String> r = Mono.just("Hello")
    .flatMap(s -> Mono.deferContextual(ctx ->
         Mono.just(s + " " + ctx.get(key)))) 
    .contextWrite(ctx -> ctx.put(key, "World")); 

StepVerifier.create(r)
            .expectNext("Hello World") 
            .verifyComplete();

3.3+ version

String key = "message";
        Mono<String> r = Mono.just("Hello")
                .flatMap( s -> Mono.subscriberContext()
                        .map( ctx -> s + " " + ctx.get(key)))
                .subscriberContext(ctx -> ctx.put(key, "World"));
 
        StepVerifier.create(r)
                .expectNext("Hello World")
                .verifyComplete();

We can see that the operation of contextWrite to set the context is at the end of the call chain. The element can be successfully obtained in its upstream operation. Subscription is from downstream to upstream. Students who have read the Reactor or RxJava source code may have a deeper understanding. To do this, you only need to set the context before calling the next method.

Example 2:

3.4.3 version

String key = "message";
Mono<String> r = Mono.just("Hello")
    .contextWrite(ctx -> ctx.put(key, "World")) 
    .flatMap( s -> Mono.deferContextual(ctx ->
        Mono.just(s + " " + ctx.getOrDefault(key, "Stranger")))); 

StepVerifier.create(r)
            .expectNext("Hello Stranger") 
            .verifyComplete();

3.3+ version 

String key = "message";
        Mono<String> r = Mono.just("Hello")
                .subscriberContext(ctx -> ctx.put(key, "World"))
                .flatMap( s -> Mono.subscriberContext()
                        .map( ctx -> s + " " + ctx.getOrDefault(key, "Stranger")));
 
        StepVerifier.create(r)
                .expectNext("Hello Stranger")
                .verifyComplete();

 

In the first example, contextWrite is at the end of the call chain. We also know that contextWrite will affect upstream operations. What if you put it in front? In the second example, the contextWrite is placed in the middle of the call chain, and you can see that the contextWrite in the middle has no effect on the subsequent flatMap. That is, contextWrite only takes effect on its upstream operations, and does not take effect on downstream operations.

Example 3:

3.4.3 version

String key = "message";
Mono<String> r = Mono
    .deferContextual(ctx -> Mono.just("Hello " + ctx.get(key)))
    .contextWrite(ctx -> ctx.put(key, "Reactor")) 
    .contextWrite(ctx -> ctx.put(key, "World")); 

StepVerifier.create(r)
            .expectNext("Hello Reactor") 
            .verifyComplete();

3.3+ version 

String key = "message";
        Mono<String> r = Mono.just("Hello")
                .flatMap( s -> Mono.subscriberContext()
                        .map( ctx -> s + " " + ctx.get(key)))
                .subscriberContext(ctx -> ctx.put(key, "Reactor"))
                .subscriberContext(ctx -> ctx.put(key, "World"));
 
        StepVerifier.create(r)
                .expectNext("Hello Reactor")
                .verifyComplete();

In the previous two examples, we saw that Reactor's contextWrite will only take effect on its upstream operations. What if there are multiple contextWrites downstream? Reactor will read the context information from the adjacent contextWrite.

Example 4:

3.4.3 version

String key = "message";
Mono<String> r = Mono
    .deferContextual(ctx -> Mono.just("Hello " + ctx.get(key))) 
    .contextWrite(ctx -> ctx.put(key, "Reactor")) 
    .flatMap( s -> Mono.deferContextual(ctx ->
        Mono.just(s + " " + ctx.get(key)))) 
    .contextWrite(ctx -> ctx.put(key, "World")); 

StepVerifier.create(r)
            .expectNext("Hello Reactor World") 
            .verifyComplete();

3.3+ version 

String key = "message";
        Mono<String> r = Mono.just("Hello")
                .flatMap( s -> Mono.subscriberContext()
                        .map( ctx -> s + " " + ctx.get(key)))
                .subscriberContext(ctx -> ctx.put(key, "Reactor"))
                .flatMap( s -> Mono.subscriberContext()
                        .map( ctx -> s + " " + ctx.get(key)))
                .subscriberContext(ctx -> ctx.put(key, "World"));
 
        StepVerifier.create(r)
                .expectNext("Hello Reactor World")
                .verifyComplete();


  In this example, the first one to obtain context information is read from the neighbor downstream to Reactor, and the second flatMap is near the World read downstream.

Example 5:

3.4.3 version

String key = "message";
Mono<String> r = Mono.just("Hello")
    .flatMap( s -> Mono
        .deferContextual(ctxView -> Mono.just(s + " " + ctxView.get(key)))
    )
    .flatMap( s -> Mono
        .deferContextual(ctxView -> Mono.just(s + " " + ctxView.get(key)))
        .contextWrite(ctx -> ctx.put(key, "Reactor")) 
    )
    .contextWrite(ctx -> ctx.put(key, "World")); 

StepVerifier.create(r)
            .expectNext("Hello World Reactor")
            .verifyComplete();

3.3+ version 

String key = "message";
        Mono<String> r =
                Mono.just("Hello")
                        .flatMap( s -> Mono.subscriberContext()
                                .map( ctx -> s + " " + ctx.get(key))
                        )
                        .flatMap( s -> Mono.subscriberContext()
                                .map( ctx -> s + " " + ctx.get(key))
                                .subscriberContext(ctx -> ctx.put(key, "Reactor"))
                        )
                        .subscriberContext(ctx -> ctx.put(key, "World"));
 
        StepVerifier.create(r)
                .expectNext("Hello World Reactor")
                .verifyComplete();

The first contextWrite only affects the internal operation flow.
The second contextWrite only affects the external main operation stream.
Through the above example, you may find that Reactor Context is so difficult to use, and you will fall into a trap if you are not careful. So when using Context, you must understand the behavior of Reactor Context. In Spring 5.x, the implementation of transactions is replaced by ThreadLocal by Reactor's Context. It is necessary to understand Context.


Processing context in Spring WebFlux

First of all, let's sort out the specific ideas. Through the above learning, we know that Reactor can handle contextual information transfer. According to the feature of Reactor Context that only takes effect upstream, we intercept the request to do a contextWrite or context information set by subscriberContext in the post-processing. In this way,
our goal can be achieved . Through the above analysis, we only use Spring WebFlux to find something similar to HandlerInterceptorAdapter in Spring MVC, and do contextWrite or subscriberContext operations in the post processor. Unfortunately, there is no HandlerInterceptorAdapter in Spring WebFlux, but the filter WebFilter can be used.

@Component
@Slf4j
public class AuthFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).subscriberContext(ctx -> {
            log.info("设置context内容。");
           return  ctx.put("token", "xx");
        });
    }

}

According to the above processing method, the context information can be obtained in our business development.

@PostMapping(name = "测试", value = "/save")
    public Mono<Test> save(@RequestBody Mono<Test> testMono) {
      
        return testService.add(testMono).log("", Level.INFO, true).flatMap(test -> {
            return Mono.subscriberContext().map(ctx -> {
                log.info("context:" + ctx.get("token"));
                return test;
            });
        });
    }

to sum up

To use Spring WebFlux to process context, you must first understand Reactor's Context processing logic. Only in this way can we avoid stepping on the pit. After all, the problem caused by the wrong information of the context is not a small problem.

 

Please indicate the source

https://blog.csdn.net/LCBUSHIHAHA/article/details/114837031

Reference

https://projectreactor.io/docs/core/release/reference/#_checking_the_execution_path_with_publisherprobe

Guess you like

Origin blog.csdn.net/LCBUSHIHAHA/article/details/114837031