前言
最近參與的項目中,接口中返回的日期格式不對,發現項目中配置了fastjson作為spring的數據轉換器,於是使用了fastjson的字段格式化轉換注解 發現不起作用。這讓我很疑惑,然后在fastjson的相關代碼中打斷點發現請求並沒有進入,最后在springmvc的流程源碼中發現最后調用的是jackson也就是springmvc的默認轉換器,fastjson沒起作用。由於在使用了@ResponseBody后才會將數據直接序列化化進響應體中,而不是渲染視圖,才有可能用到fastjson轉換器,所以跟了下springmvc的執行源碼,最終發現了原因。
我們在使用springmvc框架時,很多時候接口不是想解析視圖,而只是想把結果寫回到響應體中,例如很多時候我們只期望接口返回json或者xml格式的數據。springmvc提供了一個注解@ResponseBody。我們將其加在方法上則可以讓這個方法的返回結果經過特殊轉換后直接寫入到響應體中。當然也可以在controller的類上直接定義@RestController 。表明整個controller的方法都是將返回結果寫入到響應體中。@RestController內部其實也是就是@ResponseBody@Controller的合體
這樣我們就能返回指定格式的信息,例如在創建springboot的工程中,我們在創建controller中寫入如下方法,則可以直接返回json結果
@GetMapping(value = "/tet",produces={"application/json"} ) @ResponseBody public UserInfo get() { UserInfo a = new UserInfo("你好","word"); return a; }
或者我們將其改為xml,由於springboot默認沒有引入xml轉換器,所以我們需要加入一個包
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
@GetMapping(value = "/tet",produces={"application/xml"} )
@ResponseBody
public UserInfo get() {
UserInfo a = new UserInfo("你好","word");
return a;
}
那這個注解是如何起效果的呢,我們可以根據springmvc的流程看
正文
首先我們知道springmvc的原理是使用一個DispatchServlet來攔截servlet容器(例如jetty)所有的請求,然后根據請求的信息找到合適的處理器進行處理。我們可以看下DispatchServlet.doDispatch方法
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 { //判斷是不是multipart格式的數據 例如上傳文件 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); //1. 通過請求解析找到對應的HandlerExecutionChain //里面包含了一個符合條件的Handler 和所有符合條件的HandlerInterceptor 即springmvc的攔截器 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } //2. 獲取到該handler對應的適配器 即適配器模式 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 這兒如果get方法即防止重復調用 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //執行攔截器中的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 3. 根絕處理器適配器執行方法並返回視圖 我們由於不解析視圖 所以這兒返回為null mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } //如果mv不為null 但是沒有找到合適的視圖 就選擇一個默認的視圖 applyDefaultViewName(processedRequest, mv); //執行攔截器的postHandle方法 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); } //4.將數據寫入視圖 並且里面會執行攔截器的afterCompletion方法 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } ....... }
這個方法即大概說明了springmvc執行流程,中間也穿插了一些攔截器使用。我們由於是返回格式化數據,當然后面的視圖數據渲染步驟也就用不上了。所以將數據寫入響應體中肯定是在方法執行的步驟中就發生了。我們看下ha.handle(processedRequest, response, mappedHandler.getHandler()); 也就是方法執行的邏輯。這兒由於我們調用的是自己寫的controller層的接口,所以處理器適配器的類型為RequestMappingHandlerAdapter ,我們可以看下其繼承體系
可以看下其繼承了AbstractHandlerMethodAdapter ,我們調用的handle方法會先調用抽象類中的handle方法,但這個抽象類並沒有什么處理,直接交給子類處理了
@Override @Nullable public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
所以最終我們還是來看下RequestMappingHandlerAdapter 的handleInternal方法。
@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; //核實下方法是否執行 checkRequest(request); // 如果有同步鎖則執行這兒 if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } //否則調用這兒 else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } ................... return mav; }
這兒我們接着看普通處理 即invokeHandlerMethod方法
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); //這兒做了一堆的處理
...............
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//這兒將結果處理器設置到了ServletInvocableHandlerMethod 中
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
.............. invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
依舊是一大堆的處理,這兒就注意下webRequest中封裝了有HttpServletResponse 后面使用webRequest寫入數據會用到。還有ServletInvocableHandlerMethod 中設置了RequestMappingHandlerAdapter
中帶有的HandlerMethodReturnValueHandlerComposite,即返回結果處理器,我們后面則是使用這個來處理返回結果。
我們接着看invocableMethod.invokeAndHandle(webRequest, mavContainer);
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //獲取到方法返回結果 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); //判斷結果是否為nul if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } //判斷是否有ResponseStatusReason else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { //處理返回結果 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } }
終於,我們在這個方法的第一步獲取到了方法執行的結果,本文不主要講springmvc方法調用的過程,所以這兒不講,有興趣探究方法執行的同學可以繼續往下看。這兒獲取到了結果,那我們接下來要做的無非就是兩件事
1.判斷這個方法的處理類型 例如解析視圖,還是返回結果直接寫入方法體?
2.調用對應的數據處理器處理數據
我們可以看到在方法的最后有對結果進行處理,我們可以看下這兒是如何進行判斷的。
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { //通過返回值和 調用的方法信息找到對應的結果處理器 HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } //結果處理器處理結果 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
這兒就是兩個我們上面的說的邏輯,找到對應的結果處理器。然后處理對應的結果。
我們先看下系統是如何判斷並且找到我們需要的寫入返回結果的處理器呢?
為方法結果選擇結果處理器
@Nullable private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) { //是否同步 boolean isAsyncValue = isAsyncReturnValue(value, returnType); //迭代springmvc所有支持的結果處理器 for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { //如果結果期望同步,但是處理器不是同步結果處理器類型的 直接跳過 if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; } //判斷如何符合結果處理器的類型 會直接返回 if (handler.supportsReturnType(returnType)) { return handler; } } return null; }
這兒我們可以看到,系統是直接循環所有自帶的結果處理器,將符合的返回。 這兒需要注意了,如果有多個滿足條件,這兒找到第一個符合的就會直接返回,所以順序很重。我們看下系統的結果處理器有哪些
系統則是根據這個順序來進行循環的。系統會調用每個處理器的supportsReturnType的方法來判斷是否支持。我們可以先看下第一個ModelAndViewMethodReturnValueHandler的supportsReturnType方法
@Override public boolean supportsReturnType(MethodParameter returnType) { return ModelAndView.class.isAssignableFrom(returnType.getParameterType()); }
很明顯我們沒有返回ModelAndView,所以這個不滿足,所以一直循環到第11個 也就是RequestResponseBodyMethodProcessor的時候 我們看下其判斷邏輯
@Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); }
這兒是不是看到了比較熟悉判斷,當本次調用這個方法的類上有ResponseBody注解或者方法上有ResponseBody 注解則返回true。這兒就符合了我們一開始的聲明邏輯了。說明這個就是我們要的結果處理器了。
到現在結果處理器就已經獲取到了:RequestResponseBodyMethodProcessor ,那么接下來就是使用這個處理器來處理結果了,我們也可以看下處理的邏輯
結果處理器:RequestResponseBodyMethodProcessor 處理方法返回結果
根據上面的分析,我們獲取到結果處理器后,下一步執行的就是如下邏輯,我們可以看下其中的業務邏輯
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); //創建server相關的請求與響應 ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); //寫入結果 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
我們繼續看調用的父類的writeWithMessageConverters方法。也就是核心的寫入方法
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object outputValue; Class<?> valueType; Type declaredType; //如果返回的結果是String 特殊處理下 if (value instanceof CharSequence) { outputValue = value.toString(); valueType = String.class; declaredType = String.class; } //獲取原本的信息 else { //返回值 outputValue = value; //返回值類型 valueType = getReturnValueType(outputValue, returnType); //返回值聲明的類型 declaredType = getGenericType(returnType); } //如果是流類型的 例如InutStream 特殊處理 if (isResourceType(value, returnType)) { ........ } List<MediaType> mediaTypesToUse; //這兒查看Response中是否有手動設置ContentType MediaType contentType = outputMessage.getHeaders().getContentType(); if (contentType != null && contentType.isConcrete()) { mediaTypesToUse = Collections.singletonList(contentType); } else { //獲取請求 HttpServletRequest request = inputMessage.getServletRequest(); //獲得請求接收的MediaType 一般為*/* 即接收所有 List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request); //獲取本方法的相應產出的MediaType 如果我們沒有手動設置 那么會默認支持所有的 List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); if (outputValue != null && producibleMediaTypes.isEmpty()) { throw new HttpMessageNotWritableException( "No converter found for return value of type: " + valueType); } mediaTypesToUse = new ArrayList<>(); //找到請求接收和我們提供的匹配的MediaType 下面則會拋異常 for (MediaType requestedType : requestedMediaTypes) { for (MediaType producibleType : producibleMediaTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (mediaTypesToUse.isEmpty()) { if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; } //給所有匹配的進行特殊規則排序 //例如我們的produce中設置了兩個{"application/json","application/xml"} MediaType.sortBySpecificityAndQuality(mediaTypesToUse); } //根據一定規則挑選一個 MediaType selectedMediaType = null; for (MediaType mediaType : mediaTypesToUse) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } //如果選出來的不為null 就要根據MediaType 來挑選HttpMessageConverter對結果進行序列化並寫入了 if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); //對系統的所有消息轉換器進行迭代 for (HttpMessageConverter<?> converter : this.messageConverters) { //首選要挑選出符合GenericHttpMessageConverter的子類的轉換器 GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); //其次要判斷這個轉換器要能轉換當前數據 例如json轉換器肯定不能轉換xml數據 if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { //寫入前在對結果做一次調整 outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage); //如果不為null 則開始寫入 if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); //轉換器不為null 則調用轉換器 將結果寫入outputMessage if (genericConverter != null) { genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage); } if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType + "\" using [" + converter + "]"); } } return; } } } if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); } }
可以看出這個方法就是核心的寫入邏輯,先對結果是否是字符串做了下判斷,然后對結果是否是資源類型做了下判斷。然后分別找到請求接收的MediaType 和我們提供的MediaType ,找到二者的交集,然后按特殊順序排序,找到最優的那個MediaType ,遍歷系統的消息轉換器,如果消息轉換器是HttpMessage消息轉換器類型,且可以轉換當前消息,那么就調用消息轉換器將結果寫入outputMessage也就是我們的響應體中
這兒的消息轉換器即HttpMessageConverter即用來序列化數據的工具有些同學可能並不陌生,。因為現在很多項目都會將springmvc默認的json轉換器 jackson換為fastjson來追求更快的序列化速度,而springmvc一般自帶的如下
注意最后圈紅的需要額外引入我們一開始的包才能使用
我們設置produces = {"application/json"}時很明顯上面的篩選下只有MappingJackson2HttpMessageConverter符合
設置produces = {"application/xml"} 很明顯就只有MappingJackson2XmlHttpMessageConverter 符合
完結
到此 我們就知道了@ResponseBody的作用以及spring對這個注解的處理邏輯了,主要核心步驟如下
1.DispatchServlet的對請求進行處理,並根據請求找到合適的處理器適配器RequestMappingHandlerAdapter並調用處理方法
2.處理器適配器根據處理器中的方法特征創建ServletInvocableHandlerMethod 方法處理對象,里面包含了有返回結果處理對象HandlerMethodReturnValueHandlerComposite
3.方法處理對象中執行方法並獲得返回值
4.結果處理對象根據方法特征循環所有結果處理器找到滿足條件的結果處理器即RequestResponseBodyMethodProcessor(如果方法上有@ResponseBody注解),調用結果處理器處理結果
5.結果處理器中根據請求接收的MediaType和我們提供的MediaType進行匹配,並找到最合適的那個MediaType
6.根據MediaType遍歷所有的HttpMessageConverter找到能處理當前的MediaType的轉換器
7.轉換器將結果寫入output即響應體中
一開始的問題
模擬下項目的環境 引入fastjson包
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency>
替換轉換器的教程大致如下 省略了一些其他步驟
@Configuration public class MessageConfig implements WebMvcConfigurer { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { FastJsonHttpMessageConverter f = new FastJsonHttpMessageConverter(); f.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON)); converters.add(f); } }
相信看到這兒就理解了,我們在這兒添加會最后才添加到消息轉換器列表中,所以根據上面的查詢規則,默認的json轉換器在fastjson前面,由於默認json轉換器也是完全符合轉換要求的,所以系統當然就直接拿到默認轉換器轉換了。
通過這個圖可以知道我們定義的並未有效,那么如何處理呢? 其實有兩種方法,
第一種則是采用bean定義的方式,spring會默認掃描到這個處理器加入結果處理器中,且優先級最高
@Bean public FastJsonHttpMessageConverter init(){ FastJsonHttpMessageConverter f = new FastJsonHttpMessageConverter(); f.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); return f; }
我們再看處理器順序 即得到了我們想要的fastjson序列化
第二種則依舊采用實現WebMvcConfigurer的方式,我們在添加的時候移除掉默認的json處理器即可
然后處理器的順序 使用了fastjson