- 背景
- 实现 JsonCachingRequestWrapper
- 实现思路
- 如何使用
- 写在最后
因为Java的inputstream只能读一次,所以ServletRequest中的inputstream一旦中途被读过一次,controller层就会报错(因为再读inputstream时发现为空)
而spring提供了一个ContentCachingRequestWrapper,但是只支持application/x-www-form-urlencoded类型的请求,对于JSON是不支持的。并且它不支持多次读inputstream,只是把读完之后的内容cache了起来,然后我们可以通过getContentAsByteArray()方法获取cache住的内容。
实现 JsonCachingRequestWrapper 实现思路实现思路很简单,将inputstream读入一个byte[],后续每次需要使用inputstream时,通过byte[]实例化一个新的inputstream。当然这里还有一个知识点,servlet提供了一个HttpServletRequestWrapper基础类,让我们可以通过继承这个类来改造request的行为。下面是完整的代码:
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class JsonCachingRequestWrapper extends HttpServletRequestWrapper {
private static final String CONTENT_TYPE_JSON = "application/json";
private byte[] cache;
public JsonCachingRequestWrapper(HttpServletRequest request) {
super(request);
int contentLength = request.getContentLength();
writeRequestParametersToCachedContent(contentLength);
}
public static boolean isSupport(ServletRequest request) {
String contentType = request.getContentType();
return CONTENT_TYPE_JSON.equals(contentType);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new BodyInputStream(this.cache);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
private void writeRequestParametersToCachedContent(int contentLength) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentLength);
byte[] buffer = new byte[256];
ServletInputStream inputStream = super.getInputStream();
while (!inputStream.isFinished()) {
int nums = inputStream.read(buffer);
outputStream.write(buffer, 0, nums);
}
this.cache = outputStream.toByteArray();
} catch (IOException ex) {
throw new IllegalStateException("Failed to write request parameters to cached content", ex);
}
}
public byte[] getContentAsByteArray() {
return this.cache;
}
private static class BodyInputStream extends ServletInputStream {
private final InputStream delegate;
private boolean isFinished = false;
public BodyInputStream(byte[] body) {
this.delegate = new ByteArrayInputStream(body);
}
@Override
public boolean isFinished() {
return isFinished;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
int read = this.delegate.read();
if (read == -1) {
isFinished = true;
}
return read;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read = this.delegate.read(b, off, len);
if (read == -1) {
isFinished = true;
}
return read;
}
@Override
public int read(byte[] b) throws IOException {
int read = this.delegate.read(b);
if (read == -1) {
isFinished = true;
}
return read;
}
@Override
public long skip(long n) throws IOException {
return this.delegate.skip(n);
}
@Override
public int available() throws IOException {
return this.delegate.available();
}
@Override
public void close() throws IOException {
this.delegate.close();
}
@Override
public synchronized void mark(int readlimit) {
this.delegate.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
this.delegate.reset();
}
@Override
public boolean markSupported() {
return this.delegate.markSupported();
}
}
}
如何使用
细心的朋友会发现上面有一个方法JsonCachingRequestWrapper.isSupport(ServletRequest request).这个方法可以用来判断是否是JSON类型的请求,如果是表单类型或者是上传文件,显然用JsonCachingRequestWrapper是不合适的。
- 初始化,通常我们可以这么初始化(一般放在filter中):
if (JsonCachingRequestWrapper.isSupport(servletRequest)) {
servletRequest = new JsonCachingRequestWrapper((HttpServletRequest) servletRequest);
}
- 使用(这里以打印日志为例)
if (JsonCachingRequestWrapper.isSupport(request)) {
JsonCachingRequestWrapper requestWrapper = (JsonCachingRequestWrapper) request;
String reqParams = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
log.info("Req-log: {} {}", requestWrapper.getRequestURI(), reqParams);
}
写在最后
JavaIO的知识点还是挺多的,一开始学也比较抽象,要学的好还得懂一点操作系统,值得大家找资料仔细学习下。



