如何在Spring Boot Admin中使用InstanceExchangeFilterFunction记录Spring WebClient的JSON请求体
如何在Spring Boot Admin中使用InstanceExchangeFilterFunction记录Spring WebClient的JSON请求体
兄弟,我太懂你这个痛点了!在Spring Boot Admin(SBA)里基于Reactor栈拦截请求Payload确实比传统同步过滤器麻烦不少,官方文档这块又没讲透,踩坑很正常。你之前用同步过滤器遇到的“请求体只能读一次”的问题,本质上是因为Reactor用的是流式的请求处理,一旦消费了请求体数据流,后续流程就拿不到了——不过别担心,咱们用Reactor的方式就能解决这个问题。
核心思路:缓存请求体并重构请求
要在InstanceExchangeFilterFunction里安全读取JSON请求体,关键是先把请求体缓存下来,同时要给后续流程留一份可用的请求体。具体来说就是:
- 先读取并缓存请求体的字符串内容
- 重构一个新的
ClientRequest,把缓存的请求体重新写入 - 处理缓存的请求体(比如解析JSON、记录审计日志)
- 用重构后的请求继续执行后续流程
完整代码示例
下面是针对你的场景(拦截/actuator/loggers/的POST请求,记录日志级别修改)的完整实现:
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpMethod; import org.springframework.web.reactive.function.client.ClientRequest; import de.codecentric.boot.admin.server.web.client.InstanceExchangeFilterFunction; import java.nio.charset.StandardCharsets; @Configuration public class AuditLogInterceptorReactorConfig { private static final Logger log = LoggerFactory.getLogger(AuditLogInterceptorReactorConfig.class); private final ObjectMapper objectMapper = new ObjectMapper(); @Bean public InstanceExchangeFilterFunction auditLog() { return (instance, request, next) -> { // 只匹配目标POST请求 if (HttpMethod.POST.equals(request.method()) && request.url().getPath().contains("/actuator/loggers/")) { // 1. 缓存请求体为字符串Mono,同时释放缓冲区避免内存泄漏 Mono<String> requestBodyMono = request.body() .map(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); return new String(bytes, StandardCharsets.UTF_8); }) .single(); // 2. 重构请求,把缓存的请求体重新写入,确保后续流程能正常读取 ClientRequest modifiedRequest = ClientRequest.from(request) .body((outputMessage, context) -> requestBodyMono .map(body -> outputMessage.getBody() .write(body.getBytes(StandardCharsets.UTF_8))) .then()) .build(); // 3. 处理请求体:解析JSON并记录审计日志 return requestBodyMono.flatMap(body -> { String appName = instance.getRegistration().getName(); String path = request.url().getPath(); String loggerName = path.substring(path.indexOf("/actuator/loggers/") + "/actuator/loggers/".length()); try { JsonNode jsonBody = objectMapper.readTree(body); log.info("审计日志:应用[{}]将日志器[{}]的级别修改为[{}]", appName, loggerName, jsonBody.get("configuredLevel").asText()); } catch (JsonProcessingException e) { log.error("解析应用[{}]、日志器[{}]的请求体失败", appName, loggerName, e); } // 4. 继续执行后续请求流程 return next.exchange(modifiedRequest); }); } // 非目标请求直接放行 return next.exchange(request); }; } }
关键细节说明
- 内存泄漏防护:一定要调用
DataBufferUtils.release(dataBuffer)释放缓冲区,否则Reactor的内存会被持续占用。 - 字符编码:明确指定
StandardCharsets.UTF_8,避免因编码不一致导致的JSON解析乱码问题。 - 异常隔离:解析JSON时捕获
JsonProcessingException,保证即使请求体格式异常,也不会影响正常的请求处理流程。 - 路径匹配优化:如果想要更精确的路径匹配,可以用Spring的
PathPattern替代contains,比如PathPatternParser.DEFAULT.parse("/actuator/loggers/{loggerName}"),避免误匹配相似路径。
对你问题的直接解答
- 能不能在SBA的Reactor exchange里读取请求Payload?
当然可以!上面的代码就是专门针对SBA的InstanceExchangeFilterFunction场景实现的,完美解决了请求体只能读一次的问题。 - 有没有相关示例?
上面的完整代码就是针对你的JSON请求体场景的示例,直接复制修改就能用。
备注:内容来源于stack exchange,提问作者tero17
相关产品推荐
相关产品推荐

