如何在 Spring Boot 中覆盖@RequestBody 内容到达控制器之前?
我知道有 WebMvcConfigurer 和 HandlerInterceptorAdapter 类可以在控制器之前处理请求。
我也用谷歌搜索了RequestBodyAdviceAdapter。
有几个链接不适用于 Spring Boot。
如何多次读取request.getInputStream()
如何在 Spring Boot 中到达控制器之前修改请求正文
现在我可以将输入流读入字符串,进行一些修改并设置回控制器的输入流吗?
解决方案1:(我认为更好的解决方案) 正如评论中建议的,尝试为对象使用 @JsonProperty 或自定义 De-/Serializer。
解决方案2: 添加@ControllerAdvice并实现RequestBodyAdvice并覆盖beforeBodyRead为
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
InputStream body = inputMessage.getBody();
String bodyStr = IOUtils.toString(body, Charset.forName("UTF-8"));
/*
Update bodyStr as you wish
*/
HttpInputMessage ret = new MappingJacksonInputMessage(new ByteArrayInputStream(bodyStr.getBytes()), inputMessage.getHeaders()); //set the updated bodyStr
return ret;
}
这看起来是 Servlet 过滤器的一个很好的用例。过滤器会先于控制器接收请求,允许您修改请求,然后您可以将请求传递到过滤器链以供控制器处理。
示例: https://www.baeldung.com/spring-boot-add-filter
您将需要编写一个自定义的 HttpServletRequestWrapper。在这个包装类的构造函数中将接收请求。将InputStream读入变量(此时可以修改流)。还要重写 getInputStream() 和 getReader() 方法以返回修改后的流(而不是原始流)。
在过滤器中,创建包装器的新实例并将传入请求传递到构造函数中。完成修改后,将打包的请求传递到
chain.doFilter(myWrappedRequest, response)
以允许下游控制器处理它。
这是如何创建包装器并在过滤器中使用它的示例:https://howtodoinjava.com/servlets/httpservletrequestwrapper-example-read-request-body/
我对 Lipu 的赞扬,他直接启发了我解决类似问题的方法:我有一系列域 java 记录类,这些类将通过 Postman(或其他外部系统)通过 REST 调用提交,我需要它们全部分配一个时间戳和一个UUID,但我不想在域记录的构造函数中编写任何代码,因为外部 REST 调用会经过 Jackson 反序列化,并且无论我包含多少个更简单的构造函数,都会调用记录规范构造函数。
所以使用 Spring @ControllerAdvice 我实现了:
@ControllerAdvice
@AllArgsConstructor
public class StorableRecordAdvice extends RequestBodyAdviceAdapter {
final private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
/**
* Add the following fields to any request body:
* <li>timestamp</li>
* <li>uuid</li>
*/
@Override
public HttpInputMessage beforeBodyRead(
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType
) throws IOException {
InputStream body = inputMessage.getBody();
JsonNode jsonNode = objectMapper.readTree(IOUtils.toString(body, StandardCharsets.UTF_8));
((ObjectNode) jsonNode).put("timestamp", LocalDateTime.now(ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC));
((ObjectNode) jsonNode).put("uuid", UUID.randomUUID().toString());
return new MappingJacksonInputMessage(new ByteArrayInputStream(jsonNode.toString().getBytes()), inputMessage.getHeaders());
}
}