SpringMVC打印請(qǐng)求參數(shù)和響應(yīng)數(shù)據(jù)最優(yōu)方案
項(xiàng)目中經(jīng)常需要打印http請(qǐng)求的參數(shù)和響應(yīng)數(shù)據(jù), 但會(huì)出現(xiàn)如下問題:
- 打印Request Body參數(shù): 請(qǐng)求
content-type是application/josn格式,如果直接從HttpServletRequest中獲取輸入流, 解析請(qǐng)求數(shù)據(jù), 會(huì)導(dǎo)致SpringMVC后續(xù)的請(qǐng)求異常, 讀取輸入流異常. - 打印響應(yīng)數(shù)據(jù): 如果直接從
HttpServletResponse中獲取輸出流, 解析響應(yīng)數(shù)據(jù), 會(huì)導(dǎo)致Web服務(wù)器(Tomcat)后續(xù)響應(yīng)請(qǐng)求異常.
本文講解如何在SpringBoot中使用最優(yōu)方案實(shí)現(xiàn)該功能.
常見方案(非最優(yōu)方案)
常見方案就是通過實(shí)現(xiàn)javax.servlet.Filter對(duì)HttpServletRequest和HttpServletResponse進(jìn)行包裝, 將輸入/輸出流復(fù)制一份, 轉(zhuǎn)成字符串打印, 并且不影響后續(xù)處理.而且SpringMVC已經(jīng)為我們提供了相應(yīng)的包裝類實(shí)現(xiàn)
org.springframework.web.util.ContentCachingRequestWrapperorg.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會(huì)導(dǎo)致輸入流和輸出流被多拷貝一次 - 并不是所有的請(qǐng)求類型都適合對(duì)
HttpServletResponse包裝, 即使用Spring提供的ContentCachingResponseWrapper也無法實(shí)現(xiàn), 例如SpringMVC所支持的SSE(org.springframework.web.servlet.mvc.method.annotation.SseEmitter)請(qǐng)求,會(huì)導(dǎo)致無法向客戶端相應(yīng)數(shù)據(jù). 需要對(duì)text/event-stream請(qǐng)求做特殊處理.
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對(duì)應(yīng)的實(shí)體類類型, 不方便知道響應(yīng)請(qǐng)求的實(shí)體類類型
最優(yōu)方案
實(shí)際上SpringMVC提供了如下兩個(gè)接口,用于在解析Request Body后和響應(yīng)請(qǐng)求前回調(diào)
org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceRequestBodyAdvice用于解析Request Body后回調(diào), 可以實(shí)現(xiàn)該接口得到解析后的Body實(shí)體類,RequestBodyAdviceAdapter適配了RequestBodyAdviceorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdviceResponseBodyAdvice用于在向http請(qǐng)求響應(yīng)數(shù)據(jù)之前回調(diào), 可以實(shí)現(xiàn)該接口得到響應(yīng)的實(shí)體類
為了能夠一塊打印請(qǐng)求和響應(yīng)數(shù)據(jù), 必須在請(qǐng)求時(shí)記錄Request Body對(duì)象, 這里就考慮使用HttpServletRequest的setAttribute記錄,但是RequestBodyAdvice中無法拿到HttpServletRequest,因此需要在請(qǐng)求時(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("請(qǐng)求線程" + 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("請(qǐng)求url:{},處理方法:{}, 參數(shù):{}, 響應(yīng)參數(shù):{}", requestURI, handlerMethod, req_body, resp_body);
log.info("退出線程" + Thread.currentThread().getName() + "\n");
}
}到此這篇關(guān)于SpringMVC打印請(qǐng)求參數(shù)和響應(yīng)數(shù)據(jù)最優(yōu)方案的文章就介紹到這了,更多相關(guān)SpringMVC打印請(qǐng)求參數(shù)和響應(yīng)數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)員工工資管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)員工工資管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
Java中JavaBean對(duì)象和Map的互相轉(zhuǎn)換方法實(shí)例
為什么需要將javaBean和map進(jìn)行轉(zhuǎn)換,在很多應(yīng)用場景中,需要將key=value形式的數(shù)據(jù)與javaBean對(duì)象相互轉(zhuǎn)換,下面這篇文章主要給大家介紹了關(guān)于Java中JavaBean對(duì)象和Map的互相轉(zhuǎn)換的相關(guān)資料,需要的朋友可以參考下2022-11-11
通過實(shí)例學(xué)習(xí)Spring @Required注釋原理
這篇文章主要介紹了通過實(shí)例學(xué)習(xí)Spring @Required注釋原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
如何修改json字符串中某個(gè)key對(duì)應(yīng)的value值
這篇文章主要介紹了如何修改json字符串中某個(gè)key對(duì)應(yīng)的value值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11
maven多個(gè)倉庫查詢的優(yōu)先級(jí)順序案例講解
這篇文章主要介紹了maven多個(gè)倉庫查詢的優(yōu)先級(jí)順序,考慮到我們常用的配置文件是conf/settings.xml和工程里面的pom.xml文件,我們針對(duì)這兩個(gè)文件的結(jié)合來分析倉庫的使用順序,需要的朋友可以參考下2023-04-04

