全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-04-27_改造你的Web应用,让其支持@RequestBody内容的重复读取

您的位置:首页 >> 新闻 >> 行业资讯

改造你的Web应用,让其支持@RequestBody内容的重复读取 前言 众所周知 Spring MVC不支持多个@RequestBody注解用于同一个方法参数上。但在剖析SpringMVC内部对于@ReqeustBody注解的解析我们曾留下如下这样一段代码,并放出豪言我们有手段让SpringMVC支持如下代码的解析! @PostMapping("/duplicate") publicResponseEntitygetBookAndUserInfo(@RequestBodyBookInfobookInfo, @RequestBodyUserInfouserInfo){ BookInfoDtobookInfoDto=BookInfoDto.builder() .bookInfo(bookInfo) .userInfo(userInfo) .build(); returnnewResponseEntity(bookInfoDto,HttpStatus.OK); } 你可能会想SpringMVC内部不支持重复使用@RequestBody一定有其道理,按着规矩来就可以了,何必写成这样呢?并且上述代码完全可以将BookInfo和UserInfo封装为同一个实体,然后在进行转换即可。这样做事没错,但这次笔者想做点不一样的,希望笔者的思路能给你带来启发! @RequestBody无法重复解析的原因 在之前的分析中,我们只是简要的分析了@ReqeustBody无法重复解析的原理。这次我们通过手动Debug的方式来一行一行的分析。理论结合实践往往的更加透彻的了理解。 本次请求示例代码如下所示: @PostMapping("/duplicate") publicResponseEntitygetBookAndUserInfo(@RequestBodyBookInfobookInfo, @RequestBodyUserInfouserInfo){ BookInfoDtobookInfoDto=BookInfoDto.builder() .bookInfo(bookInfo) .userInfo(userInfo) .build(); returnnewResponseEntity(bookInfoDto,HttpStatus.OK); } 不难发现,在方法getBookAndUserInfo中的入参中通过两个@RequestBody来进行修饰。进一步,对于InvocableHandlerMethod # getMethodArgumentValues而言,其在解析入参信息时,其中的parameters数组便记录了当前被请求方法入参信息,具体如下所示: image.png由于在getBookAndUserInfo中方法入参包含两个参数,因此parameters的容量为2。因此AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters中进行参数解析的逻辑也就会被调用两次。 注:此处不熟悉相关调用逻辑可参参考:深入剖析@RequestBody无法被重复解析的原因 更进一步,两次调用readWithMessageConverters方法时,其内部在构建EmptyBodyCheckingHttpInputMessage时的逻辑如下 解析第一个参数BookInfo参数时,构建的EmptyBodyCheckingHttpInputMessage对象时的情况如下image.png解析第二个参数UserInfo参数时,构建的EmptyBodyCheckingHttpInputMessage对象时的情况如下image.png对比上图不难发现,当在第二次调用readWithMessageConverters进行构建EmptyBodyCheckingHttpInputMessage对象时,我们注意到其会将一个body成员变量置为null。 其置为null的原因,我们曾在剖析SpringMVC内部对于@ReqeustBody注解的解析中提及过,核心原因无非就是:当前I/O流已经关闭,所以无法从body请求体中读取内容! 进一步,当EmptyBodyCheckingHttpInputMessage中成员变量body被置为null后,其所诱发的连锁反应就是导致readWithMessageConverters中message.hasBody()的执行结果返回false,进而导致参数无法被解析。 具体到当前例子来看,这就导致第二个被@RequestBody修饰的UserInfo参数无法解析,进而导致本该被解析参数返回null,从而导致出现请求出现400。 image.png重复读取@RequestBody内容 那有没有一种办法让我们重复读取@ReqeustBody内容呢?答案是肯定。在提供解决方案之前,我们不妨先来看看导致@RequestBody无法重复读取原因是什么。 通过之前分析不难发现,如果一个方法入参中同时包含两个@ReqeustBody所修饰的Java对象,那么第二个被@ReqeustBody所修饰的对象在进行解析时,其在读取请求体中相关内容时存在无法解析的问题。 那导致该问题的原因是什么呢?具体来看,在EmptyBodyCheckingHttpInputMessage构造器中执行pushbackInputStream.read()时会返回一个-1。而这个-1则表示无法从当前请求体中读取相关内容。那为什么会导致这样的问题呢? 答案也很简单,就是因为SpringMVC中对于请求体的内容是通过I/O流进行处理的,当处理完毕后会将相应的I/O流进行关闭。 而@ReqeustBody内容主要封装于请求体中,如果一个请求方法中使用多个@ReqeustBody注解进行修饰,那么SpringMVC在解析时会遍历请求方法所有的入参信息,并且会重复获取请求体中的内容,以完成相应Java对象的封装。 但是请求体的中I/O在第一次解析处理后会关闭,这就导致后续再处理时,无法从请求体中获取相应内容,进而也就导致后续被@ReqeustBody修饰的对象无法完成封装。 明白了问题所在后,不知道你能否想到了相对应解决策略?此处只提供一种缓存的思路,如果你有其他好的思路也可在评论区留言~~~ 所谓缓存的思路其实也很简单,既然你对于请求体中的内容只读取一次,那么如果我们把请求体中内容进行缓存,这样你下次再调用pushbackInputStream.read()时是不是就可以读取到了,进而也就不会使得EmptyBodyCheckingHttpInputMessage中body置为null。 顺着这个思路,我们再来看EmptyBodyCheckingHttpInputMessage的构造方法。 publicEmptyBodyCheckingHttpInputMessage(HttpInputMessageinputMessage)throwsIOException{ this.headers=inputMessage.getHeaders(); InputStreaminputStream=inputMessage.getBody(); if(inputStream.markSupported()){ inputStream.mark(1); this.body=(inputStream.read()!=-1?inputStream:null); inputStream.reset(); } else{ PushbackInputStreampushbackInputStream=newPushbackInputStream(inputStream); intb=pushbackInputStream.read(); if(b==-1){ this.body=null; } else{ this.body=pushbackInputStream; pushbackInputStream.unread(b); } } } 我们注意到pushbackInputStream的读取依赖于inputMessage.getBody()。而inputMessage.getBody()则主要会获取当前请求Reqeust对象,并读取其中的I/O流信息。 进一步,既然会获取当前请求Reqeust对象,那么是不是就可以通过对Reqeust进行处理呢?只要我们读取请求中的Reqeust中请求体内容,并进行缓存我们对于请求体缓存的目标也就实现了。 更进一步,为了SpringMVC中获取的请求可以知道知晓我们所缓存的内容,我们还需要对原先Reqeuest进行替换,那什么组件能支持我们完成这样操作呢?最简单的手段无非是使用过滤器(Filter)。 相关改造代码如下所示: 缓存请求体内容,构造信息HttpServletRequestpublicclassCacheRequestBodyContentextendsHttpServletRequestWrapper{ privatebyte[]body=null; /** *Constructsarequestobjectwrappingthegivenrequest. * *@paramrequestTherequesttowrap *@throwsIllegalArgumentExceptioniftherequestisnull */ publicCacheRequestBodyContent(HttpServletRequestrequest,ServletResponseresponse){ super(request); try{ request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); body=IoUtil.readBytes(request.getInputStream(),false); }catch(Exceptione){ log.error("请求数据读取失败,请重试"); } } @Override publicBufferedReadergetReader()throwsIOException{ returnnewBufferedReader(newInputStreamReader(getInputStream())); } @Override publicServletInputStreamgetInputStream()throwsIOException{ finalByteArrayInputStreamcache=newByteArrayInputStream(body); returnnewServletInputStream(){ @Override publicintread()throwsIOException{ returncache.read(); } @Override publicintavailable()throwsIOException{ returnbody.length; } //..省略其他无关方法 } } 此处之所以还要重写其中的getReader与getInputStream方法主要为了保证pushbackInputStream.read()读取内容时,可以读取到我们所缓存的请求体内容。 定义过滤器publicclassCacheRequestFilterimplementsFilter{ @Override publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{ ServletRequestrequestWrapper=null; if(requestinstanceofHttpServletRequest){ requestWrapper=newCacheRequestBodyContent((HttpServletRequest)request,response); } if(null==requestWrapper){ chain.doFilter(request,response); }else{ chain.doFilter(requestWrapper,response); } } } 其中,通过requestWrapper = new CacheRequestBodyContent((HttpServletRequest) request, response);这行代码对默认的Request对象进行替换,以换成我我们自定义的Reqeust对象。 配置过滤器@Configuration publicclassFilterConfig{ @Bean publicFilterRegistrationBeansomeFilterRegistration(){ FilterRegistrationBeanregistration=newFilterRegistrationBean(); registration.setFilter(newCacheRequestFilter()); registration.addUrlPatterns("/*"); registration.setName("requestFilter"); registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); returnregistration; } } 配置完毕启动程序后,我们调用http://localhost:8080/users/duplicate内容,我们会可以看到起会得到如下结果: image.png显然,通过我们这样的改造使得SpringMVC内部支持了对@ReqeuestBody注解的解析。SpringMVC内部无法重复解析@ReqeustBody的问题被我们完美解决! 总结 至此我们也就利用三篇文章完成的对SpringMVC中@ReqeustBody的解析原理进行剖析,并利用相关的源码知识对SpringMVC中无法重复解析@ReqeustBody注解的问题进行解决。 如果觉得文章对你有所帮助不妨点赞+收藏+关注作者,我们下次再见! 阅读原文

上一篇:2025-03-11_从「大模型」到「具身智能」,安克深耕前沿技术的另一面藏在这里 下一篇:2025-06-25_每月省下¥70!谷歌官方Gemini免费了,还要啥自行车?

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价