您好,登錄后才能下訂單哦!
這篇文章主要介紹SpringMVC注解之@ResponseBody注解原理是什么,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!
@ResponseBody 注解的作用是將方法的返回值通過適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,寫入到 response 對象的 body 區(qū),通常用來返回 JSON、XML 數(shù)據(jù)。
使用了 @ResponseBody 注解標(biāo)記的方法不再做視圖解析
標(biāo)記在方法上
標(biāo)記在類上
通過 @RestController 注解實現(xiàn),此時所有的方法都將會被添加 @ResponseBody 注解
具體為何調(diào)用了以下方法可以看我的另一篇文章。SpringMVC 執(zhí)行流程解析
ServletInvocableHandlerMethod # invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } 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(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
該方法中調(diào)用了 handleReturnValue() 方法去處理返回值。SpringMVC 中使用 RequestResponseBodyMethodProcessor 類來處理 @ResponseBody 標(biāo)記的方法
RequestResponseBodyMethodProcessor # handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { // 設(shè)置請求已經(jīng)被完全處理了,則后面不再做視圖解析 // 后面我還會提到的 mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
該方法中通過 mavContainer.setRequestHandled(true); 設(shè)置請求已經(jīng)被完全處理了,則后面不再做視圖解析。然后調(diào)用了 writeWithMessageConverters() 方法。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { // 存儲響應(yīng)體的信息 Object body; // 返回值類型 Class<?> valueType; // 目標(biāo)類型 Type targetType; // 返回值類型是否是 CharSequence // 是則將 返回值類型和目標(biāo)類型設(shè)置為 String.class if (value instanceof CharSequence) { body = value.toString(); valueType = String.class; targetType = String.class; } // 不是 CharSequence 類型,一般是我們的自定義類 else { body = value; valueType = getReturnValueType(body, returnType); targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass()); } // 返回值類型是否是實現(xiàn)了 Resource 接口的資源 // 這里我就不分析了 if (isResourceType(value, returnType)) { outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes"); if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null && outputMessage.getServletResponse().getStatus() == 200) { Resource resource = (Resource) value; try { List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value()); body = HttpRange.toResourceRegions(httpRanges, resource); valueType = body.getClass(); targetType = RESOURCE_REGION_LIST_TYPE; } catch (IllegalArgumentException ex) { outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength()); outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value()); } } } // 選中的媒體類型 MediaType selectedMediaType = null; MediaType contentType = outputMessage.getHeaders().getContentType(); boolean isContentTypePreset = contentType != null && contentType.isConcrete(); if (isContentTypePreset) { if (logger.isDebugEnabled()) { logger.debug("Found 'Content-Type:" + contentType + "' in response"); } selectedMediaType = contentType; } else { HttpServletRequest request = inputMessage.getServletRequest(); // 可接受的媒體類型 List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); // 可產(chǎn)生的媒體類型 List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); if (body != null && producibleTypes.isEmpty()) { throw new HttpMessageNotWritableException( "No converter found for return value of type: " + valueType); } // 將要被使用的媒體類型 List<MediaType> mediaTypesToUse = new ArrayList<>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (mediaTypesToUse.isEmpty()) { if (body != null) { throw new HttpMediaTypeNotAcceptableException(producibleTypes); } if (logger.isDebugEnabled()) { logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes); } return; } 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; } } if (logger.isDebugEnabled()) { logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes); } } if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); 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); if (body != null) { Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]"); addContentDispositionHeader(inputMessage, outputMessage); if (genericConverter != null) { // 使用類型轉(zhuǎn)換器將請求寫入到 response body 中 genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body"); } } return; } } } if (body != null) { Set<MediaType> producibleMediaTypes = (Set<MediaType>) inputMessage.getServletRequest() .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) { throw new HttpMessageNotWritableException( "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'"); } throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); } }
該方法中通過調(diào)用 getAcceptableMediaTypes() 方法獲取到 acceptableTypes,getProducibleMediaTypes() 方法獲取到 producibleTypes,然后調(diào)用 isCompatibleWith() 方法比較 acceptableTypes 和 producibleTypes,獲取到兩者都兼容的類型。最后通過調(diào)用 isConcrete() 獲取到一個具體使用的媒體類型。
AbstractMessageConverterMethodProcessor # getProducibleMediaTypes
protected List<MediaType> getProducibleMediaTypes( HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) { Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (!CollectionUtils.isEmpty(mediaTypes)) { return new ArrayList<>(mediaTypes); } else if (!this.allSupportedMediaTypes.isEmpty()) { List<MediaType> result = new ArrayList<>(); // 遍歷類型轉(zhuǎn)化器,獲取支持的媒體類型 for (HttpMessageConverter<?> converter : this.messageConverters) { if (converter instanceof GenericHttpMessageConverter && targetType != null) { if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) { result.addAll(converter.getSupportedMediaTypes()); } } else if (converter.canWrite(valueClass, null)) { result.addAll(converter.getSupportedMediaTypes()); } } return result; } else { return Collections.singletonList(MediaType.ALL); } }
該方法中通過遍歷類型轉(zhuǎn)換器,根據(jù)類型轉(zhuǎn)換器獲取到支持的媒體類型。常見的類型轉(zhuǎn)化器有 StringHttpMessageConverter 支持轉(zhuǎn)換為 String 類型,MappingJackson2HttpMessageConverter 支持轉(zhuǎn)換為 json 類型,MappingJackson2XmlHttpMessageConverter 支持轉(zhuǎn)換為 XML 類型。
以轉(zhuǎn)換為 JSON 數(shù)據(jù)為例。我們最終選擇的媒體類型就是 “application/json” ,然后調(diào)用 AbstractGenericHttpMessageConverter # write 方法將數(shù)據(jù)寫入到 response body 中。
AbstractGenericHttpMessageConverter # write
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { final HttpHeaders headers = outputMessage.getHeaders(); // 添加響應(yīng)頭 // 設(shè)置 Content-Type 為 application/json addDefaultHeaders(headers, t, contentType); if (outputMessage instanceof StreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage; streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() { @Override public OutputStream getBody() { return outputStream; } @Override public HttpHeaders getHeaders() { return headers; } })); } else { // 寫入數(shù)據(jù)到 response body 中 writeInternal(t, type, outputMessage); outputMessage.getBody().flush(); } }
該方法中設(shè)置了響應(yīng)頭的 Content-Type 為 application/json,然后調(diào)用 writeInternal() 方法寫數(shù)據(jù)
AbstractJackson2HttpMessageConverter # writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // 獲取媒體類型為 application/json MediaType contentType = outputMessage.getHeaders().getContentType(); // 獲取 JSON 數(shù)據(jù)的編碼為 UTF-8 JsonEncoding encoding = getJsonEncoding(contentType); // 獲取到 HttpServletResponse 的輸出流對象 // 用于將數(shù)據(jù)寫入到 response body 中 OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody()); // 生成 JSON 數(shù)據(jù)的類 JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding); try { writePrefix(generator, object); Object value = object; Class<?> serializationView = null; FilterProvider filters = null; JavaType javaType = null; if (object instanceof MappingJacksonValue) { MappingJacksonValue container = (MappingJacksonValue) object; value = container.getValue(); serializationView = container.getSerializationView(); filters = container.getFilters(); } if (type != null && TypeUtils.isAssignable(type, value.getClass())) { // 獲取 java 類型,一般是我們自定義的類 javaType = getJavaType(type, null); } // 用于操作可序列化對象的類 // 我們自定義的類一定要實現(xiàn) Serializable 接口,并設(shè)置 get/set 方法 ObjectWriter objectWriter = (serializationView != null ? this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer()); if (filters != null) { objectWriter = objectWriter.with(filters); } if (javaType != null && javaType.isContainerType()) { objectWriter = objectWriter.forType(javaType); } SerializationConfig config = objectWriter.getConfig(); if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) { objectWriter = objectWriter.with(this.ssePrettyPrinter); } // 寫入數(shù)據(jù) objectWriter.writeValue(generator, value); writeSuffix(generator, object); generator.flush(); generator.close(); } catch (InvalidDefinitionException ex) { throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex); } catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex); } }
該方法中通過調(diào)用 outputMessage.getBody() 方法獲取到了 HttpServletResponse 的輸出流對象,用于將數(shù)據(jù)輸出到 response body 中。并設(shè)置了 JSON 數(shù)據(jù)的編碼格式為 UTF-8,然后通過 ObjectWriter 對象操作我們自定義的可序列化的對象,將該對象轉(zhuǎn)換為 JSON 格式輸出到 response body 中。
AbstractJackson2HttpMessageConverter # getJsonEncoding
protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType) { if (contentType != null && contentType.getCharset() != null) { Charset charset = contentType.getCharset(); JsonEncoding encoding = ENCODINGS.get(charset.name()); if (encoding != null) { return encoding; } } return JsonEncoding.UTF8; }
設(shè)置 JSON 數(shù)據(jù)的編碼格式為 UTF-8
ServletServerHttpResponse # getBody
public OutputStream getBody() throws IOException { this.bodyUsed = true; writeHeaders(); return this.servletResponse.getOutputStream(); }
獲取 HttpServletResponse 的輸出流
到這里我們已經(jīng)實現(xiàn)了將數(shù)據(jù)轉(zhuǎn)化為 JSON 格式輸出到 response body 中了。
還記得前面提到的 mavContainer.setRequestHandled(true) 這個方法嗎,前面我說了調(diào)用了這個方法后,就不再做視圖解析了,我們這里再具體分析一下。
RequestMappingHandlerAdapter # invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ... invocableMethod.invokeAndHandle(webRequest, mavContainer); ... return getModelAndView(mavContainer, modelFactory, webRequest); ... }
可以看到 getModelAndView() 方法是在 invokeAndHandle() 方法之后調(diào)用了,也就是在調(diào)用 getModelAndView() 方法前,我們已經(jīng)調(diào)用了 mavContainer.setRequestHandled(true) 方法了。getModelAndView() 方法就是做視圖解析的,我們來看一下該方法。
RequestMappingHandlerAdapter # getModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); // 是否已經(jīng)完全處理了,若為 true,則直接返回 null // mavContainer.setRequestHandled(true) 已設(shè)置為 true 了 if (mavContainer.isRequestHandled()) { return null; } // 下面的代碼是做視圖解析 ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; }
可以看到,該方法一開始就調(diào)用了 mavContainer.isRequestHandled() 方法,如果為 true,則返回 null,并進行下面的視圖解析。而 mavContainer.setRequestHandled(true) 方法已經(jīng)將其設(shè)置為 true 了。這就是為什么加了 @ResponseBody 注解的方法不做視圖解析的原因。
@ResponseBody 注解即可加在方法中,也可以通過 @RestController 注解加在類上
類上添加了 @RestController 注解等效于為該類的所有方法上添加 @ResponseBody 注解
@ResponseBody 通過各種類型轉(zhuǎn)換器實現(xiàn)數(shù)據(jù)的轉(zhuǎn)換,如將數(shù)據(jù)轉(zhuǎn)換為 String、JSON、XML 等格式。并將數(shù)據(jù)寫入到 response body 中。而且它們使用的都是 UTF-8 編碼。
對于自定義的 Java 類轉(zhuǎn)換為 JSON 格式的數(shù)據(jù),該類要是可序列化的。
使用了 @ResponseBody 注解標(biāo)記的方法不再做視圖解
以上是“SpringMVC注解之@ResponseBody注解原理是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。