对于前端过来的JSON数据,我们基本上都是通过服务端的IO流来解析的。如果是旧的Servlet,那么我们直接解析IO流;如果是在SpringMVC中,我们经常使用@RequestBody注解来解析。如果通过IO流解析参数,默认IO流读取一次就结束了,什么都没有了。在某些场景下,我们需要多次读取参数。举个例子:接口幂等处理,同一个接口在短时间内收到相同参数的请求,接口可能会拒绝处理。那么判断的时候需要先把请求的参数提取出来进行判断。如果是JSON参数,这个时候就会出问题。参数是提前取出来的。以后如果你在接口中获取JSON参数,你会发现它。让我们来看看如何解决这个问题。这也是松哥最近在做的TienChin项目的一个小知识点,分享给大家。新建一个SpringBoot项目,引入Web依赖。让我们一起看看下面的问题。1.问题介绍假设我现在有一个处理接口幂等性的拦截器。在这个拦截器中,我需要先获取请求的参数,然后进行比较等等,这里我就简单模拟一下,比如我们的项目中有如下几个拦截器:publicclassIdempotenceInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest请求,HttpServletResponse响应,对象处理程序)抛出异常{Strings=request.getReader().readLine();系统输出。s="+s);returntrue;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,异常ex)throwsException{}}在这个拦截器中,先把请求的参数提取出来看一下,通过IO流读取的参数最大的特点是一次性的,即读取一次就失效了。然后我们配置这个拦截器:}}看看最后的Controller接口:@RestControllerpublicclassHelloController{@PostMapping("/hello")publicvoidhello(@RequestBodyStringmsg)throwsIOException{System.out.println("msg="+msg);}}我们在接口参数上加上@RequestBody注解,底层也是通过IO流读取数据,但是由于IO流在拦截器中已经读取了一次,所以在接口中读取会出错。报错信息如下:但是,很多时候,我们希望IO流可以被多次读取,那怎么办呢?2.问题解决这里我们可以使用装饰器模式来增强HttpServletRequest的功能,具体方法也很简单,我们重新定义一个HttpServletRequest:publicclassRepeatedlyRequestWrapperextendsHttpServletRequestWrapper{privatefinalbyte[]body;publicRepeatedlyRequestWrapper(HttpServletRequest请求,ServletResponse响应)抛出IOException{super(请求);request.setCharacterEncoding("UTF-8");response.setCharacterEncoding("UTF-8");body=request.getReader().readLine().getBytes("UTF-8");}@OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader(getInputStream()));}@OverridepublicServletInputStreamgetInputStream()抛出IOException{finalByteArrayInputStreambais=newByteArrayInputStream(body);返回新的ServletInputStream(){@Overridepublicintread()throwsIOException{返回bais.read();}@Overridepublicintavailable()throwsIOException{returnbody.length;}@OverridepublicbooleanisFinished(){返回false;}@OverridepublicbooleanisReady(){返回false;}@OverridepublicvoidsetReadListener(ReadListenerreadListener){}};}}这段代码不难,很容易理解首先,在构造RepeatedlyRequestWrapper时,通过IO流读取数据并存储到字节数组中,然后重写了getReader和getInputStream方法,在读取的两个方法中IO流,都是从字节数组中返回IO流数据,从而实现重复读取。接下来,我们定义一个过滤器让这个装饰后的Request生效:ServletException{ServletRequestrequestWrapper=null;if(requestinstanceofHttpServletRequest&&StringUtils.startsWithIgnoreCase(request.getContentType(),MediaType.APPLICATION_JSON_VALUE)){requestWrapper=newRepeatedlyRequestWrapper((HttpServletRequest)请求,响应);}if(null==requestWrapper){chain.doFilter(request,response);}else{chain.doFilter(requestWrapper,response);}}@Overridepublicvoiddestroy(){}}判断,如果请求数据类型是JSON,就把HttpServletRequest“换”到RepeatedlyRequestWrapper,然后让filter继续往下走。最后配置这个过滤器:@BeanFilterRegistrationBean
