関連資料
Spring WebFlux(Reactor3)リアクティブプログラミングは例外を処理します
序文
Spring MVCでは、ThreadLocalを使用して、リクエストのコンテキストとしていくつかの情報を保存できます。一部のパブリック変数はメソッドに記述する必要はありませんが、コンテキストを介して取得できます(例:ユーザーのログイン情報)。「リファクタリング」では、Martin Fowlerは、他の方法で取得できる変数を渡すためにメソッドパラメーターを使用しないことも推奨しています。Spring WebFluxでは複数のスレッド切り替えが行われ、ThreadLocalはSpring WebFluxで役割を果たすことができません。ThreadLocalスレッド間で手動でコピーしない限り、このような処理は明らかに賢明ではありません。Spring WebFluxでコンテキスト情報と複数のスレッド間の変数の転送を処理する方法は?
リアクターコンテキスト処理
Spring WebFluxは下部でReactorを使用し、Reactorにはコールチェーンのコンテキスト情報を格納するためのコンテキスト機能があります。Spring WebFluxがコンテキスト情報を処理する方法を学ぶ前に、まずReactorがそれを処理する方法を理解しましょう。
ReactorのContextもMap構造であり、key-valueの形式で格納されます。Reactor Guideのいくつかの例を通して、Reactorがコンテキストを処理する方法を実際に見てみましょう。Reactorのコンテキストが3.4.3で改訂されたため、Spring Boot2.3.3はReactor2.3に基づいているため、両方のバージョンの例を投稿しました。
例1:
3.4.3バージョン
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以降のバージョン
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();
コンテキストを設定するためのcontextWriteの操作は、呼び出しチェーンの最後にあることがわかります。要素は、そのアップストリーム操作で正常に取得できます。サブスクリプションはダウンストリームからアップストリームへです。ReactorまたはRxJavaのソースコードを読んだ学生は、より深く理解している可能性があります。これを行うには、次のメソッドを呼び出す前にコンテキストを設定するだけです。
例2:
3.4.3バージョン
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以降のバージョン
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();
最初の例では、contextWriteはコールチェーンの最後にあります。contextWriteがアップストリーム操作に影響を与えることもわかっています。あなたがそれを前に置いたらどうしますか?2番目の例では、contextWriteが呼び出しチェーンの中央に配置されており、中央のcontextWriteが後続のflatMapに影響を与えないことがわかります。つまり、contextWriteはそのアップストリーム操作でのみ有効になり、ダウンストリーム操作では有効になりません。
例3:
3.4.3バージョン
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以降のバージョン
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();
前の2つの例では、ReactorのcontextWriteがアップストリーム操作でのみ有効になることを確認しました。ダウンストリームに複数のcontextWriteがある場合はどうなりますか?Reactorは、隣接するcontextWriteからコンテキスト情報を読み取ります。
例4:
3.4.3バージョン
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以降のバージョン
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();
この例では、コンテキスト情報を取得する最初の1つがReactorのダウンストリームのネイバーから読み取られ、2番目のflatMapがダウンストリームで読み取られるWorldの近くにあります。
例5:
3.4.3バージョン
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以降のバージョン
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();
最初のcontextWriteは、内部操作フローにのみ影響します。
2番目のcontextWriteは、外部のメイン操作ストリームにのみ影響します。
上記の例を通して、Reactor Contextの使用が非常に難しいことに気付くかもしれません。注意しないと、罠に陥ってしまいます。したがって、Contextを使用するときは、ReactorContextの動作を理解する必要があります。Spring 5.xでは、トランザクションの実装は、ReactorのコンテキストによってThreadLocalに置き換えられています。コンテキストを理解する必要があります。
SpringWebFluxでの処理コンテキスト
まず、具体的なアイデアを整理しましょう。上記の学習により、Reactorはコンテキスト情報の転送を処理できることがわかります。アップストリームでのみ有効になるReactor Contextの機能に従って、後処理でsubscriberContextによって設定されたcontextWriteまたはコンテキスト情報を実行する要求をインターセプトします。このようにして、
私たちの目標を達成することができます。上記の分析を通じて、Spring WebFluxを使用して、Spring MVCのHandlerInterceptorAdapterに類似したものを見つけ、ポストプロセッサーでcontextWriteまたはsubscriberContext操作を実行します。残念ながら、Spring WebFluxにはHandlerInterceptorAdapterはありませんが、フィルターWebFilterは使用できます。
@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");
});
}
}
上記の処理方法により、当社の事業展開においてコンテキスト情報を取得することができます。
@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;
});
});
}
総括する
Spring WebFluxを使用してコンテキストを処理するには、最初にReactorのコンテキスト処理ロジックを理解する必要があります。この方法でのみ、ピットを踏むことを避けることができます。結局のところ、コンテキストの誤った情報によって引き起こされる問題は小さな問題ではありません。
ソースを示してください
https://blog.csdn.net/LCBUSHIHAHA/article/details/114837031
参照