HttpServletRequestWrapper,Filter 和 RequestBodyAdviceAdapter
因專案需求, 配置了多個Filter
對資料進行資料過濾,並且在進入controller
之前需要進行一些日誌處理,日活統計,資料預處理等行為,所以需要多次從ServletRequest
獲取請求體資料, 但是因為HttpServletRequest
中流讀取導致的標誌位的移動, 使得資料只能讀取一次,因此利用HttpServletRequestWrapper
進行資料快取。
因為我controller
層預處理的邏輯是相同,所以通過@ControllerAdvice
並且實現RequestBodyAdviceAdapter
類方式進行了處理,在實現類中指定了需要預處理的url
路徑和複寫了beforeBodyRead
beforeBodyRead
方法中使用inputMessage.getBody().read(body)
讀取的資料永遠是body
陣列的第一個元素,原因是在實現的RequestWrapper
類中建立的ServletInputStream
物件未複寫available()
方法,導致呼叫PushbackInputStream
類中available()
方法時呼叫的是InputStream.available()
方法,返回的結果是0, 而不是陣列的實際大小,導致獲取資料異常。
下面記錄一下這幾個類的實現wrapper
實現,用於快取body資料
RequestWrapper
public class RequestWrapper extends HttpServletRequestWrapper { /** * 引數位元組陣列 */ private byte[] body; /** * Http請求物件 */ private HttpServletRequest request; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); this.request = request; } /** * 獲取輸入流, 這裡先將資料讀取出來儲存到body中 * @return * @throws IOException */ @Override public ServletInputStream getInputStream() throws IOException { if (null == this.body) { ByteArrayOutputStream outs = new ByteArrayOutputStream(); IOUtils.copy(request.getInputStream(), outs); this.body = outs.toByteArray(); } final ByteArrayInputStream in = new ByteArrayInputStream(body); return new MyServletInputStream(in); } public static class MyServletInputStream extends ServletInputStream { private ByteArrayInputStream stream; public MyServletInputStream(ByteArrayInputStream stream) { this.stream = stream; } @Override public boolean isFinished() {return false;} @Override public boolean isReady() {return false;} @Override public void setReadListener(ReadListener readListener) {} @Override public int read() throws IOException {return stream.read(); } @Override public int available() throws IOException { return stream.available(); } } public byte[] getRequestBody() { return body; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }
Filter
實現, 一般wrapper
都配合wrapper實現,這裡相對重要,因為需要將wrapper
資料放入到filter
鏈路中
ChannelFilter
public class ChannelFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } if(requestWrapper == null) { filterChain.doFilter(servletRequest, servletResponse); } else { //使用複寫後的wrapper filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { Filter.super.destroy(); } }
RequestBodyAdviceAdapter
實現類,在這個地方進行資料預處理
ExtractRequest
@Slf4j
@ControllerAdvice
public class ExtractRequest extends RequestBodyAdviceAdapter {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 這裡返回true 那麼執行後續beforeBodyRead 方法,返回false不執行
// 這裡我是依照需求, 根據了url 進行了正則判斷
String[] value = methodParameter.getContainingClass().getAnnotation(RequestMapping.class).value();
return Arrays.stream(value).anyMatch(c -> ReUtil.isMatch("/contract/.*", c));
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
// 獲取inputStream 中的body 的位元組陣列
byte[] body = new byte[inputMessage.getBody().available()];
inputMessage.getBody().read(body);
try {
// 實現自己的業務邏輯
......
} catch (Exception e) {
log.error("解析請求引數失敗:{}", e.getMessage());
}
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
一個請求傳送到tomcat
,如果存在 filter
那麼會優先執行filter
,然後才會進入servlet
容器中, 我們可以看進入到servlet
的前一步的org.apache.catalina.core.StandardWrapperValve
的invoke
方法
invoke
// StandardWrapperValve
public final void invoke(Request request, Response response)
throws IOException, ServletException {
....
// 分配一個 servlet 例項來處理這個請求
try {
if (!unavailable) {
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
......
}
// 為此請求建立過濾器鏈
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// 呼叫filter鏈
// 在這個步驟同事也呼叫了servlet的service
Container container = this.container;
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
// 在這呼叫了doFilter的方法
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
........
}
}
doFilter
方法在ApplicationFilterChain
中
doFilter
// ApplicationFilterChain
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
......
internalDoFilter(request,response);
}
在doFilter
中呼叫internalDoFilter
方法, 該方法中執行了filter
的具體doFilter
方法, 同時也執行了servlet
的service()
方法,正式進入servlet
容器
internalDoFilter
// ApplicationFilterChain
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// 這裡執行filter
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
.....
if( Globals.IS_SECURITY_ENABLED ) {
....
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// 在filter 執行完成後執行service()
try {
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
.....
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
.......
}
DispatcherServlet
下面我們來著重看一個類DispatcherServlet
, 這個類是不是看著很眼熟, 沒錯, 就是SpringMVC
的核心前端控制器,我們看一下他的繼承關係圖
上面說到在處理完filter
後進入到servlet
容器, 因為我們配置了DispatcherServlet
作為控制器, 首先請求會進入到HttpServlet.service()
方法,然後根據請求方式(這裡根據POST
方法進行說明), 進入到DispatcherServlet
的父類FrameworkServlet.doPOST()
, 最後呼叫了DispatcherSerlvet.doService()
對於資料進行處理
在doService
中進行了一些web
上下文的配置,屬性的快取等request
的全域性屬性設定, 同時進行了請求的分發doDispatcher
doDispatch
// DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 判斷是否有檔案上傳
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 從HandlerMapping獲取處理器物件,獲取對應的HandlerExecutionChain
// HandlerExecutionChain 由處理器物件和攔截器組成,攔截器相關執行都在這個類中執行
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 獲取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 如果存在攔截器, 那麼會先執行攔截器的preHandler 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 這裡是執行handler 方法,比如接收到servlet,Controller 就執行不同的Adapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 攔截器的postHandler
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
handler
是呼叫的AbstractHandlerMethodAdapter.handler()
, 然後又呼叫了RequestMappingHandlerAdapter.handleInternal()
,
RequestMappingHandlerAdapter
類在SpringMVC
中相當重要的一個類,他內部含有大量的web基礎元件來協助完成一陣個請求處理,這個類也比較龐大, 後續再新一份新的部落格進行介紹,最後在RequestMappingHandlerAdapter
類的invokeHandlerMethod
方法中會生成一個ServletInvocableHandlerMethod
物件對請求進行處理。
在執行到controller
具體的方法之前, 需要把請求的引數解析包裝成相應的實體類, 具體由InvocableHandlerMethod.getMethodArgumentValues()
方法實現, 比如解析使用了@ResquestBody
方法引數的RequestResponseBodyMethodProcessor
類, 利用readWithMessageConverters
方法讀取和轉化引數, 繼續跟著原始碼走,發現到了AbstractMessageConverterMethodArgumentResolver
中,在readWithMessageConverters
對資料進行了預先處理
readWithMessageConverters
// AbstractMessageConverterMethodArgumentResolver
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
......
EmptyBodyCheckingHttpInputMessage message = null;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
// 因為之前的ExtractRequest類只實現了beforeBodyRead,就只執行該方法
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
省略.....
}
finally {
省略.....
}
然後呼叫adviceBean
物件執行相應的切面方法,根據原始碼走到了RequestResponseBodyAdviceChain.beforeBodyRead()
如下圖所示
beforeBodyRead
方法中傳入的是EmptyBodyCheckingHttpInputMessage
例項物件,在該例項中存在兩個屬性body
,headers
,而body
則是一個PushbackInputStream
例項物件,這是一個可回退的流, 在這個例項中把之前RequestWrapper
作為in
引數傳入進去, 在ExtractRequest.beforeBodyRead
方法中對請求體獲取輸入流的位元組數的時候就會呼叫in.available()
方法, 這也是我之前未在MyServletInputStream
中複寫available()
導致踩坑的原因。