作者:冷冷gg 鏈接:https://juejin.im/post/5e93f6b6e51d4546f27ff50a
背景
- @ResponseBody 默認情況返回的數據格式是什麼?所謂默認情況 後臺接口不指定 produces MediaType
<code>@Controller public class DemoController { @ResponseBody @GetMapping(value = "/demo") public DemoVO demo() { return new DemoVO("lengleng", "123456"); } }/<code>
- 使用百度搜索 @ResponseBody 排名第一的答案, @ResponseBody 的作用其實是將 java 對象轉為 json 格式的數據。
正確答案
我們先來公佈正確的答案。
@ResponseBody 的輸出格式,默認情況取決於客戶端的 Accept 請求頭。
源碼剖析
- RequestResponseBodyMethodProcessor
<code>public class RequestResponseBodyMethodProcessor { // 處理 ResponseBody 標註的方法 @Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); } // 處理返回值 @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // 處理返回值 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } }/<code>
- writeWithMessageConverters
<code>protected void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) { HttpServletRequest request = inputMessage.getServletRequest(); // 獲取請求頭中的目標資源類型 List acceptableTypes = getAcceptableMediaTypes(request); // 獲取接口指定支持的資源類型 List producibleTypes = getProducibleMediaTypes(request, valueType, targetType); // 獲取能夠輸出資源類型 List mediaTypesToUse = new ArrayList <>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); } } } /// 排序 MediaType.sortBySpecificityAndQuality(mediaTypesToUse); for (MediaType mediaType : mediaTypesToUse) { // 判斷資源類型是否是具體的類型,而不是帶通配符 * 這種 if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } 複製代碼selectedMediaType = selectedMediaType.removeQualityValue(); // 查找支持選中資源類型的 HttpMessageConverter,輸出body for (HttpMessageConverter> converter : this.messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter>) converter : null); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class extends HttpMessageConverter>>) converter.getClass(), inputMessage, outputMessage); return; } } }/<code>
為什麼我要去研究這個問題
- 當升級至 spring cloud alibaba 2.2.1 時, sentinel 模塊 引入以下依賴
- 當依賴中出現 dataformat jar 時候, RestTemplate ,會在默認 Accept 請求頭增加
application/xml | text/xml | application/*+xml
<code>public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) { super(objectMapper, new MediaType("application", "xml", StandardCharsets.UTF_8), new MediaType("text", "xml", StandardCharsets.UTF_8), new MediaType("application", "*+xml", StandardCharsets.UTF_8)); Assert.isInstanceOf(XmlMapper.class, objectMapper, "XmlMapper required"); }/<code>
- 當我們使用 RestTemplate 調用接口時候,若不指定 Accept 會返回 XML ,導致不能平滑升級
感悟
從正式成為一名程序員的那天起,註定要進行沒有止境的學習,想要進階高級或者專家,就要堅持每天都高效的學習,不要給自己的懶惰找藉口,“什麼我也想學習可是又沒有資源”,這次我給你整理好了,我看你還有啥理由!
私信回覆【666】送你