问题描述
遇到一个需求: 在过滤器中进行请求参数进行记录。
但是发现如果使用 getReader() or getInputStream() 方法获取到POST请求的请求体后, 后续再到Controller层中使用 @RequestBody 注解就会报错:
java.lang.IllegalStateException: UT010004: Cannot call getReader(), getInputStream() already called
问题原因
ServletRequest 的设计限制了请求体只能被读取一次。一旦调用了 getInputStream() 或 getReader(),再次调用这些方法就会抛出异常。@RequestBody 解析参数时也会调用 上面两个方法
解决方案
Step1 : 自定义 CachedBodyHttpServletRequest 缓存请求体
package com.cyou.common.web.filter;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
cachedBody = request.getInputStream().readAllBytes();
}
@Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
private final ByteArrayInputStream buffer = new ByteArrayInputStream(cachedBody);
@Override
public int read() {
return buffer.read();
}
@Override
public boolean isFinished() {
return buffer.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException();
}
};
}
public byte[] getCachedBody() {
return cachedBody;
}
}
Step2 : 使用 CachedBodyHttpServletRequest缓存请求,将请求在链中传递下去
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class CachedBodyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
// 初始化方法(如果需要)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(httpRequest);
// todo 进行 POST 请求的请求体获取
String body = new String(cachedRequest.getCachedBody(), request.getCharacterEncoding());
// 使用缓存请求继续传递
chain.doFilter(cachedRequest, response);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// 销毁方法(如果需要)
}
}
Step3 : 这样就能使用@RequestBody 解析请求体了
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class ApiController {
@PostMapping("/process")
public Map<String, Object> processRequest(@RequestBody Map<String, Object> body) {
// 直接使用 @RequestBody 获取请求体
System.out.println("Controller received body: " + body);
return body;
}
}