[Análisis en profundidad de Spring Cloud Gateway] 09 ¡Enorme pozo! Orden de ejecución de GlobalFilter

Recientemente, cuando estaba escribiendo código de puerta de enlace, descubrí un problema sobre el orden de ejecución del código de GlobalFilter.

1. Introducción a la pregunta

Según tengo entendido, creo que el orden de ejecución de Filter es el siguiente:
Insertar descripción de la imagen aquí

Los tres filtros anteriores se ejecutan en orden de izquierda a derecha. Creo que la llamada en cadena de Filter es así:
el orden de ejecución debería ser:
pre0->pre1->pre2->post2->post2->post0
Sin embargo, el orden real no es así. Tomemos el código como ejemplo:

2. Demostrar la secuencia de ejecución de GlobalFilter.

Defina dos GlobalFilters, el orden es 0 y 1 respectivamente, y genere registros antes y después de chain.filter.

@Slf4j
public class FirstFilter implements GlobalFilter, Ordered {
    
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        log.info("firstFilter start");
        Mono<Void> filter = chain.filter(exchange);
        log.info("firstFilter end");
        return filter;
    }

    @Override
    public int getOrder() {
    
    
        return 0;
    }
}
@Slf4j
public class SecondFilter implements GlobalFilter, Ordered {
    
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        log.info("secondFilter start");
        Mono<Void> filter = chain.filter(exchange);
        log.info("secondFilter end");
        return filter;
    }

    @Override
    public int getOrder() {
    
    
        return 1;
    }
}

Permítanme hablar primero sobre la orden de ejecución:

firstFilter start
secondFilter start
secondFilter end
firstFilter end

Luego haga una solicitud y mire el registro. El
resultado del registro es el siguiente:

firstFilter start
firstFilter end
secondFilter start
secondFilter end

¿Nani? ¿Por qué esto es tan?
El efecto actual es en realidad así: es decir, cada filtro se ejecuta secuencialmente, después de ejecutar el primer filtro y luego ejecutar el segundo filtro.
Insertar descripción de la imagen aquí

3. Análisis del código fuente

Analicemos
cómo se ejecuta el código fuente chain.filter(exchange),
la clase de implementación correspondiente a chain es: DefaultGatewayFilterChain

@Override
public Mono<Void> filter(ServerWebExchange exchange) {
    
    
    return Mono.defer(() -> {
    
    
        if (this.index < filters.size()) {
    
    
            GatewayFilter filter = filters.get(this.index);
            DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);
            return filter.filter(exchange, chain);
        }
        else {
    
    
            return Mono.empty(); // complete
        }
    });
}

El método de filtro aquí no llama directamente al siguiente filtro con código, sino que devuelve un objeto Mono.
Los métodos definidos en Mono no se ejecutarán inmediatamente, sino que se ejecutarán después de que se ejecute el método de suscripción.

Es un poco difícil de entender aquí, así que puedes experimentarlo una y otra vez.

4. Problemas causados ​​por la ejecución secuencial.

Ahora sabemos que los filtros se ejecutan en orden, no en el orden pre1-pre2->post2->post1 como imaginamos.
Ahora hay un problema: por ejemplo, quiero usar ThreadLocal para configurar la información de contexto del usuario en el primer filtro, y luego el filtro en el medio puede obtener la información de contexto. Finalmente, borre la información del contexto del usuario. ¿Como hacer esto?
El código de muestra es el siguiente:
Defina un filtro con el orden al principio y
establezca dos métodos de devolución de llamada doFirst y doFinally a través de Mono.

@Slf4j
public class ContextSetFilter implements GlobalFilter, Ordered {
    
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        return chain.filter(exchange)
                .doFirst(() -> log.info("手动设置一下threadLocal上下文"))
                .doFinally(signalType -> log.info("手动清除一下threadLocal上下文"));
    }

    @Override
    public int getOrder() {
    
    
        return -1;
    }
}

Echemos un vistazo a los resultados de la ejecución del registro: el procesamiento de contexto se realiza al principio y al final.

手动设置一下threadLocal上下文
firstFilter start
firstFilter end
secondFilter start
secondFilter end
手动清除一下threadLocal上下文

Dirección del código fuente: https://gitee.com/syk1234/spring-cloud-new-demo.git

Supongo que te gusta

Origin blog.csdn.net/suyuaidan/article/details/132744548
Recomendado
Clasificación