Related Reading
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