SpringMVC打印請求參數(shù)和響應(yīng)數(shù)據(jù)最優(yōu)方案
項目中經(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)方案實現(xiàn)該功能.
常見方案(非最優(yōu)方案)
常見方案就是通過實現(xiàn)javax.servlet.Filter對HttpServletRequest和HttpServletResponse進行包裝, 將輸入/輸出流復(fù)制一份, 轉(zhuǎn)成字符串打印, 并且不影響后續(xù)處理.而且SpringMVC已經(jīng)為我們提供了相應(yīng)的包裝類實現(xiàn)
org.springframework.web.util.ContentCachingRequestWrapperorg.springframework.web.util.ContentCachingResponseWrapper
但在使用ContentCachingResponseWrapper時, 一定要記住必須調(diào)用ContentCachingResponseWrapper#copyBodyToResponse()將響應(yīng)數(shù)據(jù)寫回HttpServletResponse的ServletOutputStream,這樣才能成功返回數(shù)據(jù)到客戶端。
注意: 這個并不是最優(yōu)方案,并且存在諸多缺點
示例代碼如下:
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));
}這個方案存在如下幾個缺點:
- 使用包裝類包裝
HttpServletRequest和HttpServletResponse會導(dǎo)致輸入流和輸出流被多拷貝一次 - 并不是所有的請求類型都適合對
HttpServletResponse包裝, 即使用Spring提供的ContentCachingResponseWrapper也無法實現(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)的實體類類型, 不方便知道響應(yīng)請求的實體類類型
最優(yōu)方案
實際上SpringMVC提供了如下兩個接口,用于在解析Request Body后和響應(yīng)請求前回調(diào)
org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceRequestBodyAdvice用于解析Request Body后回調(diào), 可以實現(xiàn)該接口得到解析后的Body實體類,RequestBodyAdviceAdapter適配了RequestBodyAdviceorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdviceResponseBodyAdvice用于在向http請求響應(yīng)數(shù)據(jù)之前回調(diào), 可以實現(xiàn)該接口得到響應(yīng)的實體類
為了能夠一塊打印請求和響應(yīng)數(shù)據(jù), 必須在請求時記錄Request Body對象, 這里就考慮使用HttpServletRequest的setAttribute記錄,但是RequestBodyAdvice中無法拿到HttpServletRequest,因此需要在請求時通過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問題,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-11-11
SpringMVC如何獲取多種類型數(shù)據(jù)響應(yīng)
這篇文章主要介紹了SpringMVC如何獲取多種類型數(shù)據(jù)響應(yīng),本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-11-11
Java深入學(xué)習(xí)圖形用戶界面GUI之事件處理
這篇文章主要介紹了基于Java GUI 事件處理方式,一個圖形界面制作完成了,在程序開發(fā)中只是完成了起步的工作。要想讓一個組件都發(fā)揮自己的作用.就必須對所有的組件進行事件處理2022-05-05
Hibernate雙向多對多映射關(guān)系配置代碼實例
這篇文章主要介紹了Hibernate雙向多對多映射關(guān)系配置代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10
Java中Set集合轉(zhuǎn)為List集合常見的兩種方式
List是Java中比較常用的集合類,指一系列存儲數(shù)據(jù)的接口和類,可以解決復(fù)雜的數(shù)據(jù)存儲問題,這篇文章主要給大家介紹了關(guān)于Java中Set集合轉(zhuǎn)為List集合常見的兩種方式,需要的朋友可以參考下2023-12-12
Spring?Boot?集成?Quartz并使用Cron?表達式實現(xiàn)定時任務(wù)
本篇文章介紹了如何在?Spring?Boot?中集成?Quartz?進行定時任務(wù)調(diào)度,并通過?Cron?表達式?控制任務(wù)執(zhí)行時間,Quartz?提供了更強大的任務(wù)調(diào)度能力,比?@Scheduled?注解更靈活,適用于復(fù)雜的定時任務(wù)需求2025-04-04

