一、问题简述
先说下为啥有这个需求,在基于 spring 的web应用中,一般会在 controller 层获取 http 方法body中的数据。
方式1:
比如http请求的 content-type 为 application/json 的情况下,直接用 @requestbody 接收。
方式2:
也有像目前我们在做的这个项目,比较原始,是直接手动读取 流 。(不要问我为啥这么原始,第一版也不是我写的。)
1 2 3 4 5 6 |
@requestmapping ( "/xxx.do" ) public void xxx(httpservletrequest request, httpservletresponse response) throws ioexception { jsonobject jsonobject = webutils.getparameters(request); //业务处理 responseutil.setresponse(response, messagefactory.createsuccessmsg()); } |
webutils.getparameters如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static jsonobject getparameters(httpservletrequest request) throws ioexception { inputstream is = null ; is = new bufferedinputstream(request.getinputstream(), buffer_size); int contentlength = integer.valueof(request.getheader( "content-length" )); byte [] bytes = new byte [contentlength]; int readcount = 0 ; while (readcount < contentlength) { readcount += is.read(bytes, readcount, contentlength - readcount); } string requestjson = new string(bytes, appconstants.utf8); if (stringutils.isblank(requestjson)) { return new jsonobject(); } jsonobject jsonobj = jsonutils.tojsonobject(requestjson); return jsonobj; } |
当然,不管怎么说,都是对流进行读取。
问题是,假如我想在controller前面加一层aop,aop里面对进入controller层的方法进行日志记录,记录方法参数,应该怎么办呢。
如果是采用了方式1的话,简单。spring已经帮我们把参数从流里取出来,给我们提供好了,我们拿着打印一下日志即可。
如果是比较悲剧地采用了我们这种方式,参数里只有个httpservletrequest,那就只有自己去读取流了,然而,在aop中我们把流读了的话,
在controller层就读不到了。
毕竟,流只能读一次啊。
二、怎么一个流读多次呢
说一千道一万,流来自哪里,来自
1 |
javax.servlet.servletrequest#getinputstream |
所以,我们的思路,是不是可以这样,定义一个filter,在filter中将request替换为我们自定义的request。
下面标红的为自定义的request。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
/** * */ package com.ckl.filter; import com.ckl.utils.basewebutils; import com.ckl.utils.multireadhttpservletrequest; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.core.annotation.order; import org.springframework.http.httpmethod; import org.springframework.http.mediatype; import javax.servlet.*; import javax.servlet.annotation.webfilter; import javax.servlet.http.httpservletrequest; import java.io.ioexception; /** * web流多次读写过滤器 * * 拦截所有请求,主要是针对第三方提交过来的请求. * 为什么要做成可多次读写的流,因为可以在aop层打印日志。 * 但是不影响controller层继续读取该流 * * 该filter的原理:https://stackoverflow测试数据/questions/10210645/http-servlet-request-lose-params-from- post -body-after-read-it-once/17129256#17129256 * @author ckl */ @order ( 1 ) @webfilter (filtername = "cacherequestfilter" , urlpatterns = "*.do" ) public class cacherequestfilter implements filter { private static final logger logger = loggerfactory.getlogger(cacherequestfilter. class ); @override public void init(filterconfig filterconfig) throws servletexception { // todo auto-generated method stub } @override public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { httpservletrequest httpservletrequest = (httpservletrequest) request; logger.info( "request uri:{}" ,httpservletrequest.getrequesturi()); if (basewebutils.isformpost(httpservletrequest)){ httpservletrequest = new multireadhttpservletrequest(httpservletrequest); string parameters = basewebutils.getparameters(httpservletrequest); logger.info( "cacherequestfilter receive post req. body is {}" , parameters); } else if (ispost(httpservletrequest)){ //文件上传请求,没必要缓存请求 if (request.getcontenttype().contains(mediatype.multipart_form_data_value)){ } else { httpservletrequest = new multireadhttpservletrequest(httpservletrequest); string parameters = basewebutils.getparameters(httpservletrequest); logger.info( "cacherequestfilter receive post req. body is {}" , parameters); } } chain.dofilter(httpservletrequest, response); } @override public void destroy() { // todo auto-generated method stub } public static boolean ispost(httpservletrequest request) { return httpmethod.post.matches(request.getmethod()); } } multireadhttpservletrequest.java: import org.apache测试数据mons.io.ioutils; import javax.servlet.servletinputstream; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletrequestwrapper; import java.io.bufferedreader; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.io.inputstreamreader; /** * desc: * https://stackoverflow测试数据/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once/17129256#17129256 * @author : ckl * creat_date: 2018/8/2 0002 * creat_time: 13:46 **/ public class multireadhttpservletrequest extends httpservletrequestwrapper { private bytearrayoutputstream cachedbytes; public multireadhttpservletrequest(httpservletrequest request) { super (request); cachedbytes = new bytearrayoutputstream(); servletinputstream inputstream = null ; try { inputstream = super .getinputstream(); ioutils.copy(inputstream, cachedbytes); } catch (ioexception e) { e.printstacktrace(); } } @override public servletinputstream getinputstream() throws ioexception { return new cachedservletinputstream(cachedbytes); } @override public bufferedreader getreader() throws ioexception { return new bufferedreader( new inputstreamreader(getinputstream())); } } |
在自定义的request中,构造函数中,先把原始流中的数据读出来,放到bytearrayoutputstream cachedbytes中。
并且需要重新定义getinputstream方法。
以后每次程序中调用getinputstream方法时,都会从我们的偷梁换柱的request中的cachedbytes字段,new一个inputstream出来。
看上图红色部分:
getinputstream我们返回了自定义的cachedservletinputstream类。
那么,接下来是cachedservletinputstream:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package com.ceiec.webservice.utils; import javax.servlet.readlistener; import javax.servlet.servletinputstream; import java.io.bytearrayinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; /** * an inputstream which reads the cached request body */ public class cachedservletinputstream extends servletinputstream { private bytearrayinputstream input; public cachedservletinputstream(bytearrayoutputstream cachedbytes) { // create a new input stream from the cached request body byte [] bytes = cachedbytes.tobytearray(); input = new bytearrayinputstream(bytes); } @override public int read() throws ioexception { return input.read(); } @override public boolean isfinished() { return false ; } @override public boolean isready() { return false ; } @override public void setreadlistener(readlistener readlistener) { } } |
至此。完整的偷梁换柱就结束了。
现在,请再回过头去,看文章开头的代码,标红的部分。
是不是豁然开朗了?
三、代码地址
https://github测试数据/cctvckl/work_util/tree/master/spring-mvc-multiread-post
直接git 下载即可。
这是个单独的工程,直接eclipse或者idea导入即可。
运行方法:
我这边讲下idea:
直接运行jetty:run这个goal即可。
然后访问testpost.do即可(下面把curl贴出来,可以自己在接口测试工具里拼装):
1 2 3 4 5 6 |
curl -i -x post \ -h "content-type:application/json" \ -d \ '{ "id" : "32" } ' \ 'http://localhost:8080/springmvc-multiread-post/testpost.do' |
我这边演示下效果,可以发现,两次都读出来了:
总结
以上所述是小编给大家介绍的spring应用中多次读取http post方法中的流,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
原文链接:http://HdhCmsTestcnblogs测试数据/grey-wolf/p/9953661.html
查看更多关于spring应用中多次读取http post方法中的流遇到的问题的详细内容...