1. 过滤器原理和生命周期
所有的开源框架实现过滤器的模式都是大同小异的,通过一种类似职责链的方式,传统的职责链模式中的事件会传递指直到有一个处理对象接手,而过滤器和传统的职责链有点不同,所有过滤器都要进行过滤和处理,一路走到底,直到被最后一个过滤器处理
1.1. 过滤器的实现方式
在Gateway中实现一个过滤器非常简单,只要实现GatewayFilter接口的默认方法就好了
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
这里面有两个关键信息
- ServerWebExchange:这是Spring封装的HTTP request-response的交互协议,从中我们可以获取request和resposne中的各种请求参数,也可以向其中添加内容
- GatewayFilterChain:他是过滤器的调用链,在方法结束的时候我们需要将exchange对象传入调用链中的下一个对象
1.2. 过滤器的执行阶段
Gateway是通过Filter中的代码来实现类似Pre和Post的效果的
Pre和Post是指代当前过滤器的执行阶段,Pre是在下一个过滤器之前被执行,Post是在过滤器执行后再执行。我们在Gateway Filter中也可以同时定义Pre和Post执行逻辑
Pre类型和Post类型一个在过滤器执行前一个在执行后
过滤器可以排顺序的
在Gateway中可以实现 org.springframework.core.Ordered接口,来指定过滤器的执行顺序,通过实现getOrder方法
public int getOrder(){
return 0;
}
// Pre类型的过滤器来说,数字越大表示优先级越高,也就越早被执行。但对于Post类型过滤器,则是数字越小越先被执行
1.3. 过滤器示例
Header过滤器
这个系列有很多组过滤器,可以将信息加入到指定Header
.filters(f -> f.addResponseHeader("name","gateway-server"))
//相当于向header中添加一个name属性,对应的值是gateway-server
StripPrefix过滤器
这是个比较常用的过滤器,他的作用是去掉部分URL路径
.route(r -> r.path("/gateway-test/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://FEIFN-SERVICE/")
)
//假如HTTP请求访问的是/gateway-test/sample/update,如果没有StripPreix过滤器,那么转发到FEIGN-SERVIC服务的访问路径也是一样的//FEIGN-SERVICE/gateway-test/sample/update,如果添加了这个过滤器,gateway就会根据stripPrefix(1)中的配置截取URL的路径,比如这里设置的是1,那么就去掉一个前缀,最终发送给后台服务的路径就变成//FEIGN-SERVICE/sample/update
PrefixPath过滤器
他和StripPrefix的作用是完全相反的,会在请求路径的前面加入前缀
.route(r -> r.path("/gateway-test/**")
.filters(f -> f.prefixPath("go"))
.uri("lb://FEIGN-SERVICE/")
)
//假如我们访问的路径是/gateway-test/sample,如果使用这个过滤器就会变成//FEIGN-SERVICE/go/gateway-test/sample
RedirectTo过滤器
他可以把收到特定状态码的请求重定向到一个指定网址
.filters(f -> f.redirect(304,"https://www.baidu.com"))
//Caused by: java.lang.IllegalArgumentException: status must be a 3xx code, but was 404
2. 自定义过滤器实现接口计时功能
去gateway-server项目组进行修改,创建一个filter的package
package com.icodingedu.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class TimerFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//给接口计时并能输出log
StopWatch timer = new StopWatch();
//开始计时
timer.start(exchange.getRequest().getURI().getRawPath());
//我们也可以对调用链进行加工,手工放入请求参数
exchange.getAttributes().put("requestTimeBegin",System.currentTimeMillis());
return chain.filter(exchange).then(
//这里就是执行完过滤进行调用的地方
Mono.fromRunnable(() -> {
timer.stop();;
log.info(timer.prettyPrint());
})
);
}
@Override
public int getOrder() {
return 0;
}
}
去到GatewayConfiguration里设置自定义filter
@Configuration
public class GatewayConfiguration {
@Autowired
private TimerFilter timerFilter;
@Bean
@Order
public RouteLocator customerRouters(RouteLocatorBuilder builder){
LocalDateTime ldt = LocalDateTime.of(2020,10,24,21,05,10);
return builder.routes()
.route(r -> r.path("/gavinjava/**")
.and().method(HttpMethod.GET)
.filters(f -> f.stripPrefix(1)
.addResponseHeader("java-param","gateway-config")
.filter(timerFilter)
)
.uri("lb://FEIGN-CLIENT")
)
.route(r -> r.path("/secondkill/**")
.and().after(ZonedDateTime.of(ldt, ZoneId.of("Asia/Shanghai")))
.filters(f -> f.stripPrefix(1))
.uri("lb://FEIGN-CLIENT/")
)
.build();
}
}
测试后结果如下
# 这里的百分比指的是这个接口执行的时间占整个执行链路的百分比
# 1秒=1000000000(ns)9个0
---------------------------------------------
ns % Task name
---------------------------------------------
1775599286 100% /sayhello
上面定义的是针对具体的route的filter,我们也可以定义一个全局的filter直接应用在所有的route上,只需要把filter的继承修改下即可,所有route就可以自动加载了,不用调用
@Slf4j
@Component
public class TimerFilter implements GlobalFilter, Ordered
将原来config中引用的timeFilter都去掉即可