可以看下Spring常见问题解决 - @EnableWebMvc 导致自定义序列化器失效。
可以添加一个pom
依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
1.我们自定义一个过滤器MyFilter
:
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @author Zong0915
* @date 2022/8/31 下午7:36
*/
@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String requestBody = IOUtils.toString(request.getInputStream(), "utf-8");
System.out.println("print request body in filter:" + requestBody);
chain.doFilter(request, response);
}
}
2.Controller
类:
@RestController
public class MyController {
@PostMapping("/hello")
public User hello(@RequestBody User user){
return user;
}
}
3.访问对应的接口:
控制台输出如下:
这里有句话太长了,我再贴一个:
在前面的文章我讲到过关于转换器的一些问题,并且多次用一段代码来验证当前请求用的是什么转换器,代码如下AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters
:
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// ...
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
// ..对结果的转换解析
}
else {
//处理没有 body 情况,默认返回 null
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
// ..
return body;
}
我们得知,message
使用EmptyBodyCheckingHttpInputMessage
类型来进行包装,我们看下这个类的构造函数:
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
this.headers = inputMessage.getHeaders();
InputStream inputStream = inputMessage.getBody();
if (inputStream.markSupported()) {
inputStream.mark(1);
this.body = (inputStream.read() != -1 ? inputStream : null);
inputStream.reset();
}
else {
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
// 当前流是否被读取过,如果被读取过就是-1,此时this.body就赋值为null
int b = pushbackInputStream.read();
if (b == -1) {
this.body = null;
}
else {
this.body = pushbackInputStream;
pushbackInputStream.unread(b);
}
}
}
而我们在过滤器定义了这段代码:
String requestBody = IOUtils.toString(request.getInputStream(), "utf-8");
正式因为这个流被读取过了,导致在后续对请求体进行解析的时候int b = pushbackInputStream.read();
发现该流的内容已经被读取完毕了,所以请求体是空。所以报出了这样的错误:
Required request body is missing
注意:
InputStream.read
方法内部会记录position
,用于记录当前流读取到的位置。若已读完,read
方法会返回-1
。因此不能重复读取。那么我们如何解决这个问题?我们可以继续看下解析请求体的代码,有这么一段代码:
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
当一个 Body
被解析出来后,会调用 getAdvice()
来获取 RequestResponseBodyAdviceChain
;然后在这个 Chain
中,寻找合适的 Advice
并执行(即适配器)。做一些包装处理。那么我们可以基于这个特性去解决这个问题。
方式一:自定义一个适配器MyRequestBodyAdviceAdapter
来代替我们的过滤器工作。目的就是希望读取一下请求体。
@ControllerAdvice
public class MyRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
System.out.println("MyRequestBodyAdviceAdapter-afterBodyRead: body: " + body);
return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
}
不过这种方式有一点需要注意的是:
body
是Object
类型的Java对象,不再是InputStream
流。InputStream
的形式被读取两次。导致 Required request body is missing
的异常。方式二:我们依旧使用过滤器,依旧读取一遍InputStream
流。但是我们对齐进行包装,然后再返回。
我们自定义一个包装流对象BodyReaderWrapper
:
public class BodyReaderWrapper extends HttpServletRequestWrapper {
//用于将流保存下来
private byte[] requestBody;
public BodyReaderWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
String requestBodyStr = IOUtils.toString(requestBody, "utf-8");
System.out.println("print request body in filter:" + requestBodyStr);
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
注意:
getInputStream()
需要重写,读取包装类中存储的流对象。getReader()
也需要重写。过滤器做出更改:
@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 将流对象包装一下,然后返回
BodyReaderWrapper bodyReaderWrapper = new BodyReaderWrapper((HttpServletRequest) request);
chain.doFilter(bodyReaderWrapper, response);
}
}