Spring Boot 3.1.1网关过滤器Redis缓存中获取响应体问题
Spring Gateway中获取响应体并实现Redis缓存方案(Spring Boot 3.1.1)
核心逻辑
Spring Gateway基于WebFlux响应式框架,ServerHttpResponse本身不提供直接读取响应体的API——因为响应体是流式传输的。要捕获响应体,必须通过ServerHttpResponseDecorator包装原始响应,在响应体写入客户端前拦截并保存内容,同时保证响应能正常流转到客户端。
具体实现步骤
1. 自定义GatewayFilter实现缓存分支逻辑
过滤器分为两个执行分支:
- 前置校验:仅处理GET请求,先查询Redis缓存,命中则直接返回缓存内容,跳过后端服务调用。
- 后置捕获:若未命中缓存,包装响应对象,捕获后端返回的响应体并写入Redis。
2. 用ServerHttpResponseDecorator捕获响应体
重写writeWith和writeAndFlushWith方法,读取DataBuffer中的响应内容时,必须复制缓冲区(避免原始缓冲区被消费后无法返回给客户端),再将内容存入Redis。
3. 完整代码示例
自定义缓存过滤器
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; @Component public class CacheGatewayFilterFactory extends AbstractGatewayFilterFactory<CacheGatewayFilterFactory.Config> { private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate; public CacheGatewayFilterFactory(ReactiveRedisTemplate<String, String> reactiveRedisTemplate) { super(Config.class); this.reactiveRedisTemplate = reactiveRedisTemplate; } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); // 仅处理GET请求 if (!HttpMethod.GET.equals(request.getMethod())) { return chain.filter(exchange); } // 生成缓存key:请求路径+查询参数 String cacheKey = generateCacheKey(request); // 先查询Redis缓存 return reactiveRedisTemplate.opsForValue().get(cacheKey) .flatMap(cachedResponse -> { // 缓存命中,直接返回 ServerHttpResponse response = exchange.getResponse(); response.getHeaders().add("X-Cache", "HIT"); DataBuffer buffer = response.bufferFactory().wrap(cachedResponse.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)); }) .switchIfEmpty(chain.filter(exchange.mutate() .response(new ServerHttpResponseDecorator(exchange.getResponse()) { @Override public Mono<Void> writeWith(Mono<? extends DataBuffer> body) { return body.flatMap(dataBuffer -> { // 复制响应体缓冲区,避免原始数据被消费后无法返回客户端 DataBuffer copyBuffer = dataBuffer.factory().allocateBuffer(dataBuffer.readableByteCount()); copyBuffer.write(dataBuffer); // 读取响应体内容 byte[] responseBytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(responseBytes); DataBufferUtils.release(dataBuffer); String responseBody = new String(responseBytes, StandardCharsets.UTF_8); // 将响应体写入Redis并设置过期时间 return reactiveRedisTemplate.opsForValue() .set(cacheKey, responseBody, config.getExpireTime(), TimeUnit.SECONDS) .then(super.writeWith(Mono.just(copyBuffer))); }); } @Override public Mono<Void> writeAndFlushWith(Mono<? extends DataBuffer> body) { return writeWith(body); } }).build())) .doOnSuccess(v -> exchange.getResponse().getHeaders().add("X-Cache", "MISS")); }; } private String generateCacheKey(ServerHttpRequest request) { return request.getPath().value() + "?" + request.getQueryParams(); } // 配置类,用于设置缓存过期时间 public static class Config { private int expireTime = 300; // 默认5分钟 public int getExpireTime() { return expireTime; } public void setExpireTime(int expireTime) { this.expireTime = expireTime; } } }
路由配置(application.yml)
指定路由使用自定义缓存过滤器:
spring: cloud: gateway: routes: - id: service_route uri: lb://your-service-id predicates: - Path=/api/** filters: - name: CacheGatewayFilter args: expireTime: 600 # 自定义缓存过期时间为10分钟
关键注意事项
- 必须用
ReactiveRedisTemplate:Spring Gateway是响应式环境,禁用阻塞式RedisTemplate,避免阻塞事件循环导致性能问题。 - 响应体必须复制:
DataBuffer是一次性消费对象,复制后再读取内容,否则原始响应无法返回给客户端。 - 缓存key优化:可根据业务场景加入租户ID、用户标识等信息,避免多场景缓存冲突。
- 异常处理:可在过滤器中添加逻辑,后端返回错误状态码时不缓存响应。
内容的提问来源于stack exchange,提问作者Ilyazik
相关产品推荐
相关产品推荐

