最近在写网关代码时,发现一个问题,是关于GlobalFilter的代码执行顺序的问题。
以上的三个filter,从左到右的顺序执行。我认为的Filter的链式调用是这样的
执行顺序应该是:
pre0->pre1->pre2->post2->post2->post0
然而,实际顺序竟然不是这样的。还是以代码为示例吧:
定义两个GlobalFilter,顺序分别为0和1,分别在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;
}
}
先说一下,我以为的执行顺序:
firstFilter start
secondFilter start
secondFilter end
firstFilter end
然后请求一下,看一下日志
日志输出如下:
firstFilter start
firstFilter end
secondFilter start
secondFilter end
纳尼?为会么会是这样?
现在的效果实际上是这样的:即每一个filter顺序执行,执行完第一个filter,再执行第2个filter
再分析一下源码
chain.filter(exchange)到底是怎么执行的
chain对应的实现类是: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
}
});
}
这里的filter方法,并不是用代码直接调用下一个filter!而是返回的一个Mono对象!
Mono里面定义的方法,并不会马上执行,而是在subscribe方法执行后,才会运行。
这里有点难以理解,可以反复体会一下。
现在我们知道了,filter是按顺序执行的,而不是我们想象中的pre1-pre2->post2->post1这种顺序。
现在有一个问题,比如我想在第一个filter,最开始比如用ThreadLocal设置一下用户的上下文信息,然后中间的filter就可以获取到上下文信息了。最后在清除用户上下文信息。这个要怎么做到?
示例代码如下:
定义一个Filter,顺序在最前面
通过Mono设置两个回调方法doFirst和doFinally
@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;
}
}
再来看一下日志执行的结果,分别在最开始和最后,执行了上下文的处理。
手动设置一下threadLocal上下文
firstFilter start
firstFilter end
secondFilter start
secondFilter end
手动清除一下threadLocal上下文
源码地址:https://gitee.com/syk1234/spring-cloud-new-demo.git