新聞中心
一、問(wèn)題簡(jiǎn)述
創(chuàng)新互聯(lián)建站主營(yíng)云安網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,App定制開發(fā),云安h5小程序定制開發(fā)搭建,云安網(wǎng)站營(yíng)銷推廣歡迎云安等地區(qū)企業(yè)咨詢
先說(shuō)下為啥有這個(gè)需求,在基于spring的web應(yīng)用中,一般會(huì)在controller
層獲取http方法body中的數(shù)據(jù)。
方式1:
比如http請(qǐng)求的content-type
為application/json
的情況下,直接用@RequestBody
接收。
方式2:
也有像目前我們?cè)谧龅倪@個(gè)項(xiàng)目,比較原始,是直接手動(dòng)讀取流。(不要問(wèn)我為啥這么原始,第一版也不是我寫的。)
@RequestMapping("/XXX.do") public void XXX(HttpServletRequest request, HttpServletResponse response) throws IOException { JSONObject jsonObject = WebUtils.getParameters(request); //業(yè)務(wù)處理 ResponseUtil.setResponse(response, MessageFactory.createSuccessMsg()); }
WebUtils.getParameters如下:
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; }
當(dāng)然,不管怎么說(shuō),都是對(duì)流進(jìn)行讀取。
問(wèn)題是,假如我想在controller前面加一層aop,aop里面對(duì)進(jìn)入controller層的方法進(jìn)行日志記錄,記錄方法參數(shù),應(yīng)該怎么辦呢。
如果是采用了方式1的話,簡(jiǎn)單。spring已經(jīng)幫我們把參數(shù)從流里取出來(lái),給我們提供好了,我們拿著打印一下日志即可。
如果是比較悲劇地采用了我們這種方式,參數(shù)里只有個(gè)httpServletRequest,那就只有自己去讀取流了,然而,在aop中我們把流讀了的話,
在controller層就讀不到了。
畢竟,流只能讀一次啊。
二、怎么一個(gè)流讀多次呢
說(shuō)一千道一萬(wàn),流來(lái)自哪里,來(lái)自
javax.servlet.ServletRequest#getInputStream
所以,我們的思路,是不是可以這樣,定義一個(gè)filter,在filter中將request替換為我們自定義的request。
下面標(biāo)紅的為自定義的request。
/** * */ 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流多次讀寫過(guò)濾器 * * 攔截所有請(qǐng)求,主要是針對(duì)第三方提交過(guò)來(lái)的請(qǐng)求. * 為什么要做成可多次讀寫的流,因?yàn)榭梢栽赼op層打印日志。 * 但是不影響controller層繼續(xù)讀取該流 * * 該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)){ //文件上傳請(qǐng)求,沒(méi)必要緩存請(qǐng)求 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中,構(gòu)造函數(shù)中,先把原始流中的數(shù)據(jù)讀出來(lái),放到ByteArrayOutputStream cachedBytes中。
并且需要重新定義getInputStream方法。
以后每次程序中調(diào)用getInputStream方法時(shí),都會(huì)從我們的偷梁換柱的request中的cachedBytes字段,new一個(gè)InputStream出來(lái)。
看上圖紅色部分:
getInputStream我們返回了自定義的CachedServletInputStream類。
那么,接下來(lái)是CachedServletInputStream:
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) { } }
至此。完整的偷梁換柱就結(jié)束了。
現(xiàn)在,請(qǐng)?jiān)倩剡^(guò)頭去,看文章開頭的代碼,標(biāo)紅的部分。
是不是豁然開朗了?
三、代碼地址
https://github.com/cctvckl/work_util/tree/master/spring-mvc-multiread-post
直接git 下載即可。
這是個(gè)單獨(dú)的工程,直接eclipse或者idea導(dǎo)入即可。
運(yùn)行方法:
我這邊講下idea:
直接運(yùn)行jetty:run這個(gè)goal即可。
然后訪問(wèn)testPost.do即可(下面把curl貼出來(lái),可以自己在接口測(cè)試工具里拼裝):
curl -i -X POST \ -H "Content-Type:application/json" \ -d \ '{"id":"32"} ' \ 'http://localhost:8080/springmvc-multiread-post/testPost.do'
我這邊演示下效果,可以發(fā)現(xiàn),兩次都讀出來(lái)了:
總結(jié)
以上所述是小編給大家介紹的spring應(yīng)用中多次讀取http post方法中的流,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)創(chuàng)新互聯(lián)網(wǎng)站的支持!
當(dāng)前名稱:spring應(yīng)用中多次讀取httppost方法中的流遇到的問(wèn)題
URL鏈接:http://fisionsoft.com.cn/article/gphjdd.html