Java中调用不可修改的JSON读取方法两次报错的解决求助
解决HttpServletRequest输入流仅能读取一次的问题
问题背景
现有两个无法修改的方法readAndParseJSON()和readJSON(),当先后传入同一个HttpServletRequest对象调用时,会抛出Exception while parsing JSON string: Unexpected token END OF FILE at position 0错误。原因是HttpServletRequest的输入流只能被读取一次,第二次调用时流已经为空。
相关调用代码
JSONObject jsonObject = (JSONObject) JSONReaderAndParser.readAndParseJSON(req); if (jsonObject.get("abc") != null) Boolean abc = Boolean.parseBoolean(jsonObject.get("abc").toString()); fun(JSONReaderAndParser.readJSON(request),abc);
不可修改的目标方法实现
public static Object readAndParseJSON(HttpServletRequest request) throws Exception { Object object; try { JSONParser parser = new JSONParser(); StringBuilder jsonString = new StringBuilder(); String data = null; data = IOUtils.toString(request.getInputStream(), "UTF-8"); jsonString.append(data); object = parser.parse(jsonString.toString()); } catch (IOException e) { throw new Exception("Server error"); } catch (ParseException e) { throw new Exception("bad request"); } if (object == null) { throw new Exception("Invalid json"); } return object; } public static String readJSON(HttpServletRequest request) throws Exception { StringBuffer jsonString = new StringBuffer(); String data = null; try { data = IOUtils.toString(request.getInputStream(), "UTF-8"); jsonString.append(data); } catch (Exception e) { throw new Exception(); } if (data == null) { throw new Exception("Parsed object is null"); } return jsonString.toString(); }
解决方案
核心思路是通过自定义HttpServletRequestWrapper包装原始请求,提前缓存输入流的内容,让输入流可以被重复读取。具体步骤如下:
1. 实现自定义请求包装类
这个类会把输入流内容读取到字节数组中缓存,后续每次调用getInputStream()时都返回基于缓存数组的新流:
import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; public class CachedRequestWrapper extends HttpServletRequestWrapper { private final byte[] cachedBody; public CachedRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 读取原始输入流并缓存 cachedBody = IOUtils.toByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { // 返回基于缓存字节数组的新流 return new CachedServletInputStream(cachedBody); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } // 实现自定义的ServletInputStream private static class CachedServletInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public CachedServletInputStream(byte[] cachedBody) { this.inputStream = new ByteArrayInputStream(cachedBody); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(javax.servlet.ReadListener readListener) { // 按需实现,此处留空不影响核心功能 } } }
2. 修改调用逻辑,使用包装后的请求
在调用目标方法前,先把原始请求包装成CachedRequestWrapper,后续所有方法调用都使用这个包装对象:
// 先包装原始请求,缓存输入流内容 CachedRequestWrapper cachedReq = new CachedRequestWrapper(req); // 用包装对象调用第一个方法 JSONObject jsonObject = (JSONObject) JSONReaderAndParser.readAndParseJSON(cachedReq); Boolean abc = null; if (jsonObject.get("abc") != null) { abc = Boolean.parseBoolean(jsonObject.get("abc").toString()); } // 继续用同一个包装对象调用第二个方法 fun(JSONReaderAndParser.readJSON(cachedReq), abc);
这样两个方法都能读取到完整的输入流内容,避免了流为空的错误。
内容的提问来源于stack exchange,提问作者mosid
相关产品推荐
相关产品推荐

