SpringMVC打印請求參數(shù)和響應(yīng)數(shù)據(jù)最優(yōu)方案
項(xiàng)目中經(jīng)常需要打印http請求的參數(shù)和響應(yīng)數(shù)據(jù), 但會出現(xiàn)如下問題:
- 打印Request Body參數(shù): 請求
content-type
是application/josn
格式,如果直接從HttpServletRequest
中獲取輸入流, 解析請求數(shù)據(jù), 會導(dǎo)致SpringMVC后續(xù)的請求異常, 讀取輸入流異常. - 打印響應(yīng)數(shù)據(jù): 如果直接從
HttpServletResponse
中獲取輸出流, 解析響應(yīng)數(shù)據(jù), 會導(dǎo)致Web服務(wù)器(Tomcat)后續(xù)響應(yīng)請求異常.
本文講解如何在SpringBoot中使用最優(yōu)方案實(shí)現(xiàn)該功能.
常見方案(非最優(yōu)方案)
常見方案就是通過實(shí)現(xiàn)javax.servlet.Filter
對HttpServletRequest
和HttpServletResponse
進(jìn)行包裝, 將輸入/輸出流復(fù)制一份, 轉(zhuǎn)成字符串打印, 并且不影響后續(xù)處理.而且SpringMVC已經(jīng)為我們提供了相應(yīng)的包裝類實(shí)現(xiàn)
org.springframework.web.util.ContentCachingRequestWrapper
org.springframework.web.util.ContentCachingResponseWrapper
但在使用ContentCachingResponseWrapper
時(shí), 一定要記住必須調(diào)用ContentCachingResponseWrapper#copyBodyToResponse()
將響應(yīng)數(shù)據(jù)寫回HttpServletResponse
的ServletOutputStream
,這樣才能成功返回?cái)?shù)據(jù)到客戶端。
注意: 這個(gè)并不是最優(yōu)方案,并且存在諸多缺點(diǎn)
示例代碼如下:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if (isStream(request)) { filterChain.doFilter(request, response); return; } boolean isFirstRequest = !isAsyncDispatch(request); HttpServletRequest requestToUse = request; ContentCachingResponseWrapper responseToUse = new ContentCachingResponseWrapper(response); if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) { requestToUse = new ContentCachingRequestWrapper(request); } try { filterChain.doFilter(requestToUse, responseToUse); } finally { accessLog(requestToUse, responseToUse); responseToUse.copyBodyToResponse(); } } private boolean isStream(HttpServletRequest request) { return MediaType.TEXT_EVENT_STREAM_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.ACCEPT)) || MediaType.TEXT_EVENT_STREAM_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.CONTENT_TYPE)); }
這個(gè)方案存在如下幾個(gè)缺點(diǎn):
- 使用包裝類包裝
HttpServletRequest
和HttpServletResponse
會導(dǎo)致輸入流和輸出流被多拷貝一次 - 并不是所有的請求類型都適合對
HttpServletResponse
包裝, 即使用Spring提供的ContentCachingResponseWrapper
也無法實(shí)現(xiàn), 例如SpringMVC所支持的SSE
(org.springframework.web.servlet.mvc.method.annotation.SseEmitter
)請求,會導(dǎo)致無法向客戶端相應(yīng)數(shù)據(jù). 需要對text/event-stream
請求做特殊處理.
SpringWeb 5.1.14版本中ShallowEtagHeaderFilter#disableContentCaching
方法的注釋中已經(jīng)說明
/** * This method can be used to disable the content caching response wrapper * of the ShallowEtagHeaderFilter. This can be done before the start of HTTP * streaming for example where the response will be written to asynchronously * and not in the context of a Servlet container thread. * @since 4.2 */ public static void disableContentCaching(ServletRequest request) { Assert.notNull(request, "ServletRequest must not be null"); request.setAttribute(STREAMING_ATTRIBUTE, true); }
不方便獲取Request Body對應(yīng)的實(shí)體類類型, 不方便知道響應(yīng)請求的實(shí)體類類型
最優(yōu)方案
實(shí)際上SpringMVC提供了如下兩個(gè)接口,用于在解析Request Body后和響應(yīng)請求前回調(diào)
org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice
RequestBodyAdvice用于解析Request Body后回調(diào), 可以實(shí)現(xiàn)該接口得到解析后的Body實(shí)體類,RequestBodyAdviceAdapter適配了RequestBodyAdviceorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
ResponseBodyAdvice用于在向http請求響應(yīng)數(shù)據(jù)之前回調(diào), 可以實(shí)現(xiàn)該接口得到響應(yīng)的實(shí)體類
為了能夠一塊打印請求和響應(yīng)數(shù)據(jù), 必須在請求時(shí)記錄Request Body對象, 這里就考慮使用HttpServletRequest
的setAttribute
記錄,但是RequestBodyAdvice
中無法拿到HttpServletRequest
,因此需要在請求時(shí)通過ThreadLocal
綁定. 見示例ThreadBindRequestContainer
@Slf4j @ControllerAdvice public class RequestResponseBodyAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { HttpServletRequest request = ThreadBindRequestContainer.getServletRequest(); Method method = parameter.getMethod(); String declaringClass = method.getDeclaringClass().getSimpleName(); String handlerMethod = method.toString().substring(method.toString().indexOf(declaringClass)); request.setAttribute("handlerMethod", handlerMethod); request.setAttribute("req_body", body); return body; } @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); servletRequest.setAttribute("resp_body", body); return body; } }
public class ThreadBindRequestContainer { private static final ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>(); public static void bind(HttpServletRequest request) { threadLocal.set(request); } public static void remove() { threadLocal.remove(); } public static HttpServletRequest getServletRequest() { return threadLocal.get(); } }
@Slf4j public class RequestResponseBodyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ThreadBindRequestContainer.bind(request); log.info("請求線程" + Thread.currentThread().getName()); return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void afterCompletion(HttpServletRequest req, HttpServletResponse response, Object handler, Exception ex) throws Exception { ThreadBindRequestContainer.remove(); String requestURI = req.getRequestURI(); Object req_body = req.getAttribute("req_body"); Object resp_body = req.getAttribute("resp_body"); Object handlerMethod = req.getAttribute("handlerMethod"); log.info("請求url:{},處理方法:{}, 參數(shù):{}, 響應(yīng)參數(shù):{}", requestURI, handlerMethod, req_body, resp_body); log.info("退出線程" + Thread.currentThread().getName() + "\n"); } }
到此這篇關(guān)于SpringMVC打印請求參數(shù)和響應(yīng)數(shù)據(jù)最優(yōu)方案的文章就介紹到這了,更多相關(guān)SpringMVC打印請求參數(shù)和響應(yīng)數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Intellij IDEA中的Version Control問題
這篇文章主要介紹了Intellij IDEA中的Version Control問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11SpringMVC如何獲取多種類型數(shù)據(jù)響應(yīng)
這篇文章主要介紹了SpringMVC如何獲取多種類型數(shù)據(jù)響應(yīng),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11Java深入學(xué)習(xí)圖形用戶界面GUI之事件處理
這篇文章主要介紹了基于Java GUI 事件處理方式,一個(gè)圖形界面制作完成了,在程序開發(fā)中只是完成了起步的工作。要想讓一個(gè)組件都發(fā)揮自己的作用.就必須對所有的組件進(jìn)行事件處理2022-05-05Hibernate雙向多對多映射關(guān)系配置代碼實(shí)例
這篇文章主要介紹了Hibernate雙向多對多映射關(guān)系配置代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10Java中Set集合轉(zhuǎn)為List集合常見的兩種方式
List是Java中比較常用的集合類,指一系列存儲數(shù)據(jù)的接口和類,可以解決復(fù)雜的數(shù)據(jù)存儲問題,這篇文章主要給大家介紹了關(guān)于Java中Set集合轉(zhuǎn)為List集合常見的兩種方式,需要的朋友可以參考下2023-12-12Spring?Boot?集成?Quartz并使用Cron?表達(dá)式實(shí)現(xiàn)定時(shí)任務(wù)
本篇文章介紹了如何在?Spring?Boot?中集成?Quartz?進(jìn)行定時(shí)任務(wù)調(diào)度,并通過?Cron?表達(dá)式?控制任務(wù)執(zhí)行時(shí)間,Quartz?提供了更強(qiáng)大的任務(wù)調(diào)度能力,比?@Scheduled?注解更靈活,適用于復(fù)雜的定時(shí)任務(wù)需求2025-04-04