相关阅读
Spring WebFlux(Reactor3)重试
Spring WebFlux(Reactor3)响应式编程处理异常
在Spring MVC中我们可以使用ThreadLocal保存一些信息,作为请求的上下文。一些公用的变量就不用写在方法中,可以通过上下文来获取(比如:用户的登录信息)。在《重构》中马丁福勒也推荐在能通过其他方式获取到的变量时,不要使用方法参数传递进来。在Spring WebFlux中会存在多次线程切换,ThreadLocal在Spring WebFlux中就无法发挥作用了,除非手动的做ThreadLocal线程间的复制,这样处理明显是不明智的行为。那在Spring WebFlux中如何处理上下文信息,多线程间变量的传递问题?
Spring WebFlux底层使用Reactor,在Reactor中有一个Context特性,用来存放调用链的上下文信息。在学习Spring WebFlux处理上下文信息的方式前,我们先了解一下Reactor是如何处理的。
Reactor中的Context也是一个Map结构,以key-value的形式存储。下面通过几个来自Reactor Guide的例子实际看看,Reactor是怎么处理上下文的。由于Reactor的Context在3.4.3做了改版,Spring Boot 2.3.3是基于Reactor 3.3+的,所以两个版本的例子我都贴上来了。
例子1:
3.4.3版本
String key = "message";
Mono 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+版本
String key = "message";
Mono 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();
我们可以看到contextWrite设置上下文的操作在调用链的最尾端。在其上游的操作中能成功获取到元素。订阅是从下游流上上游的,看过Reactor或者RxJava源码的同学可能会理解深一点,要做到这点只用在调用next方法前将上下文设置进去就可以了。
例子2:
3.4.3版本
String key = "message";
Mono 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+版本
String key = "message";
Mono 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();
第一个例子中contextWrite在调用链的最尾端,我们也知道了contextWrite会对上游的操作产生影响。那如果放在前面呢?第二个例子将contextWrite放到了调用链的中间,可以看到中间的contextWrite没有对后面的flatMap产生影响。也就是contextWrite只对它上游的操作生效,对下游的操作不生效。
例子3:
3.4.3版本
String key = "message";
Mono 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+版本
String key = "message";
Mono 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();
在前面的两个例子中,我们看到Reactor的contextWrite只会对其上游的操作生效,那如果再下游有多个contextWrite呢?Reactor会从邻近的contextWrite中读取上下文的信息。
例子4:
3.4.3版本
String key = "message";
Mono 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+版本
String key = "message";
Mono 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();
这个例子中第一个获取上下文的信息从邻近下游读取到Reactor,第二个flatMap邻近下游读取到的World。
例子5:
3.4.3版本
String key = "message";
Mono 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+版本
String key = "message";
Mono 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();
第一个contextWrite只影响内部的操作流。
第二个contextWrite只会影响外部主操作流。
通过上面的例子,可能你会觉得Reactor Context好难用,一不小心就会掉到陷阱中。所以在使用Context的时候,一定要理解清楚Reactor Context的行为。在Spring 5.x中事务的实现就由Reactor的Context替换了ThreadLocal,理解Context是很有必要的。
首先,理一下具体的思路。通过上面的学习我们知道Reactor可以处理上下文的信息传递,根据Reactor Context只对上游生效的特性,我们拦截请求在后置处理处做一次contextWrite或subscriberContext设置的上下文信息。这样就可以达到
我们的目的了。通过上面的分析,我们只用在Spring WebFlux中找到和Spring MVC中HandlerInterceptorAdapter类似的东西,在后置处理器中做contextWrite或subscriberContext操作就可以了。遗憾的是在Spring WebFlux中没有HandlerInterceptorAdapter,但是可以使用过滤器WebFilter。
@Component
@Slf4j
public class AuthFilter implements WebFilter {
@Override
public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange).subscriberContext(ctx -> {
log.info("设置context内容。");
//注意这里一定要return ctx.put。put操作是产生一个新的context。如果是return ctx;会导致值没有设置进去
return ctx.put("token", "xx");
});
}
}
按照上面的处理方式,在我们业务开发中就能取到上下文的信息了。
@PostMapping(name = "测试", value = "/save")
public Mono save(@RequestBody Mono testMono) {
return testService.add(testMono).log("", Level.INFO, true).flatMap(test -> {
return Mono.subscriberContext().map(ctx -> {
log.info("context:" + ctx.get("token"));
return test;
});
});
}
如果想设置多个值可以像下面这样操作。
@Component
public class WebFluxFilter implements WebFilter {
@Override
public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.subscriberContext(ctx -> {
return ctx.put("token", "123123123123");
})
.subscriberContext(ctx -> {
return ctx.put("liu", "hah");
});
}
}
总结
使用Spring WebFlux处理上下文,要先了解Reactor的Context处理逻辑。这样才能避免踩坑,毕竟上下文的信息错取引发的问题可不是小问题。
转载请注明出处
https://blog.csdn.net/LCBUSHIHAHA/article/details/114837031
参考资料
https://projectreactor.io/docs/core/release/reference/#_checking_the_execution_path_with_publisherprobe