GlobalCacheRequestBodyFilter和GlobalLoginOrRegisterFilter的意义

来源:7-11 登录、注册、鉴权全局过滤器(上)

cqnuhy

2026-02-26

老师原话是:

[00:02]这节课里呢,我们再去给大家去实现一个全局过滤器 [00:06]这个过滤器呢,是用于去缓存HDP请求的BODY [00:10]而且呢,它是缓存某一类请求的BODY [00:13]那么,之所以要这样做的原因呢 [00:15]是因为我们将来呢,会通过我们的网关 [00:18]去访问到我们的这个健全微服务 [00:21]也就是我们的authority这样的一个微服务 [00:23]那么通过authority呢,我们可以去完成注册 [00:26]可以去完成登录,就是获取token的过程 [00:29]我们这个获取token以登录的过程呢 [00:31]也会去通过网关里面的这个filter去实现 [00:34]也就通过过滤器实现 [00:36]但是你要知道,你需要去登录到我们的系统呢 [00:39]或者是注册到我们的系统呢 [00:40]你需要去提供用户名和密码 [00:42]而且这样的请求需要是post的类型的 [00:45]但是呢,我们在filter里面呢 [00:47]是拿不到这样的post请求的数据的 [00:50]所以说呢,我们需要有一个过滤器 [00:52]去把用户的请求的数据呢,给它缓存下来 [00:56]在我们的另一个过滤器里面 [00:57]去实现登录和这个实现注册的时候呢 [01:00]才能够拿到这样的一个请求数据

」 我理解是不是这样个意思?

疑问 1:既然说 filter 拿不到 POST 请求数据,为何缓存过滤器还能拿到?

完整逻辑是:

不是 “拿不到”,而是 **“只能拿一次”**:Spring WebFlux 中ServerHttpRequest.getBody()返回的Flux<DataBuffer>一旦被订阅(消费),数据流就会被耗尽,后续再调用getBody()拿到的 Flux 就没有数据了;

缓存过滤器能拿到数据的核心原因:它是第一个执行的过滤器(优先级更高),此时请求体的原始 Flux 还未被任何组件消费,所以DataBufferUtils.join(exchange.getRequest().getBody())能正常订阅并合并所有分块数据;

缓存过滤器的本质是 “抢在所有人之前消费原始请求体,然后创建可重复读取的副本”,并非 “拿不到”,而是要解决 “后续拿不到” 的问题。

疑问 2:Flux 数据一次性,为何登录过滤器还能读取?

核心是缓存过滤器重写了getBody()方法,替换了原始的一次性 Flux:

原始 Flux 是一次性的:缓存过滤器通过DataBufferUtils.join消费了原始 Flux(此时原始 Flux 已耗尽);

缓存过滤器创建了 “可重复读取” 的新 Flux:

DataBufferUtils.retain(dataBuffer)保留数据缓冲区,防止被自动释放;

Flux.defer(() -> Flux.just(dataBuffer.slice(...)))创建的cachedFlux是 “可重复订阅” 的 —— 每次调用getBody()订阅这个 Flux,都会返回一个新的 DataBuffer 切片(不是消耗原数据);

登录过滤器读取的是 “新 Flux”:后续过滤器拿到的是ServerHttpRequestDecorator(重写了getBody()),调用getBody()拿到的是cachedFlux,而非原始的一次性 Flux,因此能正常读取。

疑问 3:跨过滤器的 retain 和 release 为何会影响同一份内存?

答案是:两个过滤器操作的是「共享底层内存」的 DataBuffer 对象,不是独立对象,核心逻辑:

DataBuffer 的本质:DataBuffer 是对底层内存缓冲区(如 Netty 的 ByteBuf)的封装,slice()方法不会复制底层内存,只是调整了读写索引 —— 缓存过滤器中dataBuffer.slice()创建的切片,和原 DataBuffer 共享同一块底层内存;

引用计数机制:retain()和release()操作的是底层内存的引用计数(类似 JVM 的引用计数 GC):

缓存过滤器中DataBufferUtils.retain(dataBuffer):给底层内存的引用计数 + 1,防止消费后被自动释放;

登录过滤器中DataBufferUtils.release(buffer):这里的buffer是切片后的 DataBuffer,release 会让引用计数 - 1;

内存泄漏的根源:如果只 retain 不 release,引用计数始终 > 0,底层内存永远不会被释放,最终导致内存泄漏;登录过滤器的 release 是为了抵消缓存过滤器的 retain,让引用计数归 0,释放底层内存。

为何跨过滤器有效:因为切片和原 DataBuffer 共享底层内存,引用计数是全局的(不是每个过滤器独立),所以跨过滤器的 retain/release 会直接影响同一块内存的生命周期。



写回答

1回答

张勤一

2026-02-28

非常感谢同学你的回复,你所说的这些都是正确的,也非常难得你这么认真!

0
0

Spring Cloud / Alibaba 微服务架构实战

从架构设计到开发实践,手把手实现

1240 学习 · 679 问题

查看课程