You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 04:52:34