在接触了SpringCloudGateway时,需要给路由添加一个日志记录的过滤器,难点出现在获取RequestBody上。
在阅读源码时发现了全局过滤器AdaptCachedBodyGlobalFilter
,可以将RequestBody缓存到exchange中。
它的执行优先级非常高(注释编号:A),利用这一点,我们可以让这个全局网关过滤器工作,缓存requestBody,然后在自定义过滤器中读取requestBody,进行日志记录:
先来看AdaptCachedBodyGlobalFilter
的源码(版本:3.0.3.RELEASE):
1 | java复制代码public class AdaptCachedBodyGlobalFilter implements GlobalFilter, Ordered, ApplicationListener<EnableBodyCachingEvent> { |
我们看到(注释编号:B)想要ServerWebExchangeUtils.cacheRequestBody(exchange,functions)
方法执行(也就是缓存requestBody)的必要条件有两个:
- Databuffer没有被缓存到exchange的attributes对象中。
- 路由被标记为需要缓存,也就是
this.routesToCache.containsKey(rouceId)
方法必须返回true。
第一个条件不必多说,那么如何满足第二个条件呢?看到代码(注释编号:C),当本过滤器接收到事件EnableBodyCachingEvent
时,会将路由ID,保存到this.routesToCache
中。因此,只需要我们的自定义过滤器LogInfoGatewayFilter
发送出EnableBodyCachingEvent事件,框架就会自动为我们缓存requestBody。因而在本过滤器被执行的时候,就不再需要自建ServerHttpRquest的装饰类了。
下一个问题:如何发送EnableBodyCachingEvent
事件?
搜索源码发现,只有RetryGatewayFilterFactory
重试过滤器工厂这个类发送了事件,其实也很好理解,当路由中设置了重试过滤器,最简便的方式就是事先缓存好请求数据。(这里不会深入RetryGatewayFilterFactory的源码 ,只需要我们从中了解如何发送RetryGatewayFilterFactory就可以了)
继续阅读代码:
1 | java复制代码public GatewayFilter apply(String routeId, Repeat<ServerWebExchange> repeat, Retry<ServerWebExchange> retry) { |
发现所有继承了AbstractGatewayFilterFactory<C>
抽象过滤器网关的类都会一起继承方法getPublisher()
,来获取事件推送器publier,用来发送EnableBodyCachingEvent事件。
接下来我们如何获取routeId?
同样也是参照RetryGatewayFilterFactory类,发现他的配置类RetryConfig
实现了HasRouteId
接口,当在RouteDefinitionRouteLocator
在加载过滤器的时候,会将路由的id赋值到RetryConfig
中。因此,可以模仿RetryConfig编写一个filter的配置类。
1 | java复制代码public static class LogConfig implements HasRouteId { |
做好了以上的准备,自定义日志过滤器LogInfoGatewayFilterFactory
在被加载时,就会将开启缓存的事件发送给AdaptCachedBodyGlobalFilter
,将routeId缓存起来。AdaptCachedBodyGlobalFilter在网关接收到网络请求的时候,将requestBody缓存到exchange中。
日志过滤器工厂类的代码如下:
1 | java复制代码/** |
日志网关过滤的代码如下:
1 | java复制代码/** |
过滤器配置示例:
1 | yml复制代码spring: |
添加LogInfo的过滤器,在启动的时候,就可以输出RquestBody啦
注:以上的代码还只在本地环境中测试,至于在正式环境中表现如何,还需要经过实践考验。
本文转载自: 掘金