Spring Cloud Gateway如何基于请求体JSON内容路由至对应服务?
在Spring Cloud Gateway中,我已在yaml文件配置了两个路径相同的路由,分别指向http://localhost:8081/和http://localhost:8082/,对应两个同URI的POST接口。现需根据请求JSON体中codiceRicevitoria字段的值(为1时调用8081服务,否则调用8082服务)实现路由,请问该如何实现?当前自定义GatewayFilter的思路是否正确,还是使用自定义Predicate更合适?
我的配置与代码示例如下:
yaml配置:
spring: cloud: gateway: routes: - id: springcloud uri: http://localhost:8081/ predicates: - Path=/springcloud/** filters: - name: InterceptRouteGatewayFilter - id: springcloud2 uri: http://localhost:8082/ predicates: - Path=/springcloud/** filters: - name: InterceptRouteGatewayFilter
Java代码:
@Component public class InterceptRouteGatewayFilter extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); Flux<DataBuffer> body = request.getBody(); return body.collectList().flatMap(dataBuffers -> { byte[] contentBytes = dataBuffers.stream() .map(buffer -> { byte[] bytes = new byte[buffer.readableByteCount()]; buffer.read(bytes); return bytes; }) .reduce(new byte[0], (a, b) -> { byte[] result = new byte[a.length + b.length]; System.arraycopy(a, 0, result, 0, a.length); System.arraycopy(b, 0, result, a.length, b.length); return result; }); String bodyJson = new String(contentBytes, StandardCharsets.UTF_8); String result= extractCodiceRicevitoria(bodyJson); if (result.equals("1")) { // CALL http://localhost:8081/springcloud/microservice } else { // CALL http://localhost:8082/springcloud/microservice } return chain.filter(exchange); }); }; } private String extractCodiceRicevitoria(String requestBody) { JsonObject jsonObject = new JsonParser().parse(requestBody).getAsJsonObject(); return jsonObject.get("codiceRicevitoria").getAsString(); } }
思路选择:自定义Predicate更合适
Spring Cloud Gateway中,Predicate的职责是判断请求是否匹配当前路由,而Filter主要负责请求/响应的修改逻辑。你的需求是根据请求体字段选择路由,用自定义RoutePredicateFactory更贴合Gateway的设计理念,路由规则更清晰。
而你当前的Filter思路存在问题:两个路由都匹配Path=/springcloud/**,会同时进入匹配逻辑,Filter无法直接拦截路由选择,只能修改请求URI,反而会让配置冗余。
下面提供两种可行方案:
方案一:自定义RoutePredicateFactory(推荐)
通过自定义Predicate,让每个路由根据请求体的codiceRicevitoria值决定是否匹配,完全符合Gateway的路由匹配逻辑。
1. 实现自定义Predicate类
@Component public class CodiceRicevitoriaRoutePredicateFactory extends AbstractRoutePredicateFactory<CodiceRicevitoriaRoutePredicateFactory.Config> { public CodiceRicevitoriaRoutePredicateFactory() { super(Config.class); } @Override public Predicate<ServerWebExchange> apply(Config config) { return exchange -> { ServerHttpRequest request = exchange.getRequest(); // 读取并缓存请求体(请求体只能读取一次) return DataBufferUtils.join(request.getBody()) .map(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); // 解析JSON字段 String body = new String(bytes, StandardCharsets.UTF_8); JsonObject jsonObject = new JsonParser().parse(body).getAsJsonObject(); String codice = jsonObject.get("codiceRicevitoria").getAsString(); // 缓存请求体供后续使用 exchange.getAttributes().put("cachedRequestBody", bytes); // 判断是否匹配当前路由的目标值 return codice.equals(config.getTargetValue()); }) .defaultIfEmpty(false) .block(); }; } // 接收yaml配置参数的内部类 public static class Config { private String targetValue; public String getTargetValue() { return targetValue; } public void setTargetValue(String targetValue) { this.targetValue = targetValue; } } @Override public List<String> shortcutFieldOrder() { return Collections.singletonList("targetValue"); } }
2. 修改yaml配置
给每个路由添加自定义Predicate,同时通过ModifyRequestBody过滤器恢复缓存的请求体(否则后续服务无法读取):
spring: cloud: gateway: routes: - id: springcloud-8081 uri: http://localhost:8081/ predicates: - Path=/springcloud/** - CodiceRicevitoria=1 filters: - name: ModifyRequestBody args: inClass: java.lang.String outClass: java.lang.String rewriteFunction: | (exchange, body) -> { byte[] cachedBody = exchange.getAttribute("cachedRequestBody"); return cachedBody != null ? new String(cachedBody, java.nio.charset.StandardCharsets.UTF_8) : body; } - id: springcloud-8082 uri: http://localhost:8082/ predicates: - Path=/springcloud/** - CodiceRicevitoria!=1 # 匹配非1的所有情况 filters: - name: ModifyRequestBody args: inClass: java.lang.String outClass: java.lang.String rewriteFunction: | (exchange, body) -> { byte[] cachedBody = exchange.getAttribute("cachedRequestBody"); return cachedBody != null ? new String(cachedBody, java.nio.charset.StandardCharsets.UTF_8) : body; }
方案二:自定义GatewayFilter(单路由动态修改URI)
如果不想配置多个路由,可以用单路由+Filter动态修改目标URI的方式,适合简单场景。
1. 修改yaml配置
spring: cloud: gateway: routes: - id: springcloud uri: http://localhost:8081/ # 默认指向8081 predicates: - Path=/springcloud/** filters: - name: DynamicUriGatewayFilter
2. 实现自定义Filter类
@Component public class DynamicUriGatewayFilter extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); return DataBufferUtils.join(request.getBody()) .flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); // 解析请求体字段 String bodyJson = new String(bytes, StandardCharsets.UTF_8); JsonObject jsonObject = new JsonParser().parse(bodyJson).getAsJsonObject(); String codice = jsonObject.get("codiceRicevitoria").getAsString(); // 动态生成目标URI URI newUri = "1".equals(codice) ? URI.create("http://localhost:8081" + request.getPath()) : URI.create("http://localhost:8082" + request.getPath()); // 重新包装请求,恢复请求体(避免后续无法读取) ServerHttpRequestDecorator decoratedRequest = new ServerHttpRequestDecorator(request.mutate().uri(newUri).build()) { @Override public Flux<DataBuffer> getBody() { return Flux.just(exchange.getResponse().bufferFactory().wrap(bytes)); } }; return chain.filter(exchange.mutate().request(decoratedRequest).build()); }); }; } }
- 请求体只能读取一次:无论哪种方案,都必须缓存并恢复请求体,否则后续服务或过滤器无法获取请求内容。
- 异常处理:要添加JSON解析失败、字段缺失等异常捕获逻辑,避免网关崩溃。
- 性能考量:高并发场景下,缓存请求体可能占用内存,建议限制请求体大小,或使用流式处理优化。
内容的提问来源于stack exchange,提问作者R.S.

