您现在的位置是:亿华云 > 域名

JSON 数据读一次就没了,怎么办?

亿华云2025-10-03 02:22:28【域名】7人已围观

简介对于前端传来的 JSON 数据,我们在服务端基本上都是通过 IO 流来解析,如果是古老的 Servlet,那么我们直接解析 IO 流;如果是在 SpringMVC 中,我们往往通

对于前端传来的怎么办 JSON 数据,我们在服务端基本上都是数据通过 IO 流来解析,如果是读次古老的 Servlet,那么我们直接解析 IO 流;如果是怎么办在 SpringMVC 中,我们往往通过 @RequestBody 注解来解析。数据

如果通过 IO 流来解析参数,读次默认情况下,怎么办IO 流读一次就结束了,数据就没有了。读次而往往有些场景,怎么办需要我们多次读取参数,数据我举一个例子:

接口幂等性的读次处理,同一个接口,怎么办在短时间内接收到相同参数的数据请求,接口可能会拒绝处理。读次那么在判断的时候,就需要先把请求的参数提取出来进行判断,如果是 JSON 参数,此时就会有问题,参数提前取出来了,将来在接口中再去获取 JSON 参数,服务器租用就会发现没有了。

我们来看看这个问题怎么解决,这也是最近松哥在做的 TienChin 项目的一个小知识点,和大家分享下。

新建一个 Spring Boot 项目,引入 Web 依赖,我们一起来看下面的问题。

1. 问题演示

假设我现在有一个处理接口幂等性的拦截器,在这个拦截器中,我需要先获取到请求的参数,然后进行比对等等,我这里先简单模拟一下,比如我们项目中有如下拦截器:

public class IdempotenceInterceptor implements HandlerInterceptor {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

String s = request.getReader().readLine();

System.out.println("s = " + s);

return true;

}

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}

}

在这个拦截器中先把请求的参数拎出来,瞅一眼。通过 IO 流读取出来的参数最大特点是一次性,也就是读一次就失效了。

然后我们配置一下这个拦截器:

@Configuration

public class WebConfig implements WebMvcConfigurer {

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new IdempotenceInterceptor()).addPathPatterns("/**");

}

}

最后再来看看 Controller 接口:

@RestController

public class HelloController {

@PostMapping("/hello")

public void hello(@RequestBody String msg) throws IOException {

System.out.println("msg = " + msg);

}

}

在接口参数上我们加了 @RequestBody 注解,这个底层也是通过 IO 流来读取数据的,源码下载但是由于 IO 流在拦截器中已经被读取过一次了,所以到了接口中再去读取就会出错。报错信息如下:

然而很多时候,我们希望 IO 流能够被多次读取,那么怎么办呢?

2. 问题解决

这里我们可以利用装饰者模式对 HttpServletRequest 的功能进行增强,具体做法也很简单,我们重新定义一个 HttpServletRequest:

public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {

private final byte[] body;

public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {

super(request);

request.setCharacterEncoding("UTF-8");

response.setCharacterEncoding("UTF-8");

body = request.getReader().readLine().getBytes("UTF-8");

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(getInputStream()));

}

@Override

public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bais = new ByteArrayInputStream(body);

return new ServletInputStream() {

@Override

public int read() throws IOException {

return bais.read();

}

@Override

public int available() throws IOException {

return body.length;

}

@Override

public boolean isFinished() {

return false;

}

@Override

public boolean isReady() {

return false;

}

@Override

public void setReadListener(ReadListener readListener) {

}

};

}

}

这段代码并不难,很好懂。

首先在构造 RepeatedlyRequestWrapper 的时候,就通过 IO 流将数据读取出来并存入到一个 byte 数组中,然后重写 getReader 和 getInputStream 方法,在这两个读取 IO 流的方法中,都从 byte 数组中返回 IO 流数据出来,这样就实现了反复读取了。

接下来我们定义一个过滤器,让这个装饰后的 Request 生效:

public class RepeatableFilter implements Filter {

@Override

public void init(FilterConfig filterConfig) throws ServletException {

}

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throws IOException, ServletException {

ServletRequest requestWrapper = null;

if (request instanceof HttpServletRequest

&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {

requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);

}

if (null == requestWrapper) {

chain.doFilter(request, response);

} else {

chain.doFilter(requestWrapper, response);

}

}

@Override

public void destroy() {

}

}

判断一下,如果请求数据类型是 JSON 的话,就把 HttpServletRequest “偷梁换柱”改为 RepeatedlyRequestWrapper,然后让过滤器继续往下走。

最后再配置一下这个过滤器:

@Bean

FilterRegistrationBeanrepeatableFilterBean() {

FilterRegistrationBeanbean = new FilterRegistrationBean<>();

bean.addUrlPatterns("/*");

bean.setFilter(new RepeatableFilter());

return bean;

}

好啦大功告成。

以后,我们的 JSON 数据就可以通过 IO 流反复读取了。站群服务器

很赞哦!(92)