一、問題簡述
先說下為啥有這個需求,在基于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.com/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.commons.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.com/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.com/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://www.cnblogs.com/grey-wolf/p/9953661.html