Procesamiento de contexto Spring WebFlux (Reactor3)

Lectura relacionada

Spring WebFlux (Reactor3) 重 试

La programación reactiva de Spring WebFlux (Reactor3) maneja excepciones

Prefacio

En Spring MVC, podemos usar ThreadLocal para guardar cierta información como contexto de la solicitud. Algunas variables públicas no necesitan escribirse en el método, pero se pueden obtener a través del contexto (por ejemplo: la información de inicio de sesión del usuario). En "Refactorización", Martin Fowler también recomienda no utilizar parámetros de método para pasar variables que se pueden obtener por otros medios. Habrá múltiples cambios de subprocesos en Spring WebFlux, y ThreadLocal no puede desempeñar un papel en Spring WebFlux. A menos que copie manualmente entre subprocesos ThreadLocal, tal procesamiento es obviamente imprudente. ¿Cómo lidiar con la información contextual y la transferencia de variables entre múltiples hilos en Spring WebFlux?

Procesamiento del contexto del reactor

Spring WebFlux usa Reactor en la parte inferior, y hay una función de contexto en Reactor para almacenar la información de contexto de la cadena de llamadas. Antes de aprender cómo Spring WebFlux maneja la información contextual, primero comprendamos cómo Reactor la maneja.

El contexto en Reactor también es una estructura de mapa, almacenada en forma de clave-valor. Echemos un vistazo práctico a cómo Reactor maneja el contexto a través de algunos ejemplos de la Guía de Reactor. Dado que el contexto de Reactor se ha revisado en 3.4.3, Spring Boot 2.3.3 se basa en Reactor 2.3, por lo que he publicado ambas versiones de los ejemplos.

Ejemplo 1:

3.4.3 versión

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();

Versión 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();

Podemos ver que la operación de contextWrite para establecer el contexto está al final de la cadena de llamadas. El elemento se puede obtener con éxito en su operación upstream. La suscripción es de nivel descendente a ascendente. Los estudiantes que hayan leído el código fuente de Reactor o RxJava pueden tener una comprensión más profunda. Para hacer esto, solo necesita establecer el contexto antes de llamar al siguiente método.

Ejemplo 2:

3.4.3 versión

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();

Versión 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();

 

En el primer ejemplo, contextWrite se encuentra al final de la cadena de llamadas. También sabemos que contextWrite afectará las operaciones ascendentes. ¿Y si lo pones al frente? En el segundo ejemplo, contextWrite se coloca en el medio de la cadena de llamadas, y puede ver que contextWrite en el medio no tiene ningún efecto en el flatMap posterior. Es decir, contextWrite solo tiene efecto en sus operaciones ascendentes y no tiene efecto en las operaciones descendentes.

Ejemplo 3:

3.4.3 versión

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();

Versión 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();

En los dos ejemplos anteriores, vimos que el contextWrite de Reactor solo tendrá efecto en sus operaciones ascendentes. ¿Qué pasa si hay varios contextWrites descendentes? Reactor leerá la información de contexto del contextWrite adyacente.

Ejemplo 4:

3.4.3 versión

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();

Versión 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();


  En este ejemplo, el primero en obtener información de contexto se lee desde el vecino aguas abajo de Reactor, y el segundo flatMap está cerca de la lectura mundial aguas abajo.

Ejemplo 5:

3.4.3 versión

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();

Versión 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();

El primer contextWrite solo afecta el flujo de operaciones interno.
El segundo contextWrite solo afecta al flujo de operaciones principal externo.
A través del ejemplo anterior, puede encontrar que Reactor Context es tan difícil de usar, y caerá en una trampa si no tiene cuidado. Entonces, al usar Context, debe comprender el comportamiento de Reactor Context. En Spring 5.x, la implementación de transacciones es reemplazada por ThreadLocal por el Contexto de Reactor Es necesario entender el Contexto.


Contexto de procesamiento en Spring WebFlux

En primer lugar, clasifiquemos las ideas específicas. A través del aprendizaje anterior, sabemos que Reactor puede manejar la transferencia de información contextual De acuerdo con la característica de Reactor Context que solo tiene efecto en sentido ascendente, interceptamos la solicitud para hacer un contextWrite o información de contexto establecida por subscriberContext en el posprocesamiento. De esta manera,
nuestro objetivo se puede lograr . A través del análisis anterior, solo usamos Spring WebFlux para encontrar algo similar a HandlerInterceptorAdapter en Spring MVC y realizar operaciones contextWrite o subscriberContext en el postprocesador. Desafortunadamente, no hay HandlerInterceptorAdapter en Spring WebFlux, pero se puede usar el filtro 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");
        });
    }

}

De acuerdo con el método de procesamiento anterior, la información de contexto se puede obtener en nuestro desarrollo comercial.

@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;
            });
        });
    }

para resumir

Para utilizar Spring WebFlux para procesar el contexto, primero debe comprender la lógica de procesamiento de contexto de Reactor. Solo así podremos evitar pisar el hoyo Después de todo, el problema causado por la información incorrecta del contexto no es un problema menor.

 

Por favor indique la fuente

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

Referencia

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

Supongo que te gusta

Origin blog.csdn.net/LCBUSHIHAHA/article/details/114837031
Recomendado
Clasificación