您好,登錄后才能下訂單哦!
這篇文章主要介紹SpringMVC注解之@ResponseBody注解原理是什么,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
@ResponseBody 注解的作用是將方法的返回值通過適當的轉換器轉換為指定的格式之后,寫入到 response 對象的 body 區,通常用來返回 JSON、XML 數據。
使用了 @ResponseBody 注解標記的方法不再做視圖解析
標記在方法上
標記在類上
通過 @RestController 注解實現,此時所有的方法都將會被添加 @ResponseBody 注解
具體為何調用了以下方法可以看我的另一篇文章。SpringMVC 執行流程解析
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; } }
該方法中調用了 handleReturnValue() 方法去處理返回值。SpringMVC 中使用 RequestResponseBodyMethodProcessor 類來處理 @ResponseBody 標記的方法
RequestResponseBodyMethodProcessor # handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { // 設置請求已經被完全處理了,則后面不再做視圖解析 // 后面我還會提到的 mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
該方法中通過 mavContainer.setRequestHandled(true); 設置請求已經被完全處理了,則后面不再做視圖解析。然后調用了 writeWithMessageConverters() 方法。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { // 存儲響應體的信息 Object body; // 返回值類型 Class<?> valueType; // 目標類型 Type targetType; // 返回值類型是否是 CharSequence // 是則將 返回值類型和目標類型設置為 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()); } // 返回值類型是否是實現了 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); // 可產生的媒體類型 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) { // 使用類型轉換器將請求寫入到 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); } }
該方法中通過調用 getAcceptableMediaTypes() 方法獲取到 acceptableTypes,getProducibleMediaTypes() 方法獲取到 producibleTypes,然后調用 isCompatibleWith() 方法比較 acceptableTypes 和 producibleTypes,獲取到兩者都兼容的類型。最后通過調用 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<>(); // 遍歷類型轉化器,獲取支持的媒體類型 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); } }
該方法中通過遍歷類型轉換器,根據類型轉換器獲取到支持的媒體類型。常見的類型轉化器有 StringHttpMessageConverter 支持轉換為 String 類型,MappingJackson2HttpMessageConverter 支持轉換為 json 類型,MappingJackson2XmlHttpMessageConverter 支持轉換為 XML 類型。
以轉換為 JSON 數據為例。我們最終選擇的媒體類型就是 “application/json” ,然后調用 AbstractGenericHttpMessageConverter # write 方法將數據寫入到 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(); // 添加響應頭 // 設置 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 { // 寫入數據到 response body 中 writeInternal(t, type, outputMessage); outputMessage.getBody().flush(); } }
該方法中設置了響應頭的 Content-Type 為 application/json,然后調用 writeInternal() 方法寫數據
AbstractJackson2HttpMessageConverter # writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // 獲取媒體類型為 application/json MediaType contentType = outputMessage.getHeaders().getContentType(); // 獲取 JSON 數據的編碼為 UTF-8 JsonEncoding encoding = getJsonEncoding(contentType); // 獲取到 HttpServletResponse 的輸出流對象 // 用于將數據寫入到 response body 中 OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody()); // 生成 JSON 數據的類 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); } // 用于操作可序列化對象的類 // 我們自定義的類一定要實現 Serializable 接口,并設置 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); } // 寫入數據 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); } }
該方法中通過調用 outputMessage.getBody() 方法獲取到了 HttpServletResponse 的輸出流對象,用于將數據輸出到 response body 中。并設置了 JSON 數據的編碼格式為 UTF-8,然后通過 ObjectWriter 對象操作我們自定義的可序列化的對象,將該對象轉換為 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; }
設置 JSON 數據的編碼格式為 UTF-8
ServletServerHttpResponse # getBody
public OutputStream getBody() throws IOException { this.bodyUsed = true; writeHeaders(); return this.servletResponse.getOutputStream(); }
獲取 HttpServletResponse 的輸出流
到這里我們已經實現了將數據轉化為 JSON 格式輸出到 response body 中了。
還記得前面提到的 mavContainer.setRequestHandled(true) 這個方法嗎,前面我說了調用了這個方法后,就不再做視圖解析了,我們這里再具體分析一下。
RequestMappingHandlerAdapter # invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ... invocableMethod.invokeAndHandle(webRequest, mavContainer); ... return getModelAndView(mavContainer, modelFactory, webRequest); ... }
可以看到 getModelAndView() 方法是在 invokeAndHandle() 方法之后調用了,也就是在調用 getModelAndView() 方法前,我們已經調用了 mavContainer.setRequestHandled(true) 方法了。getModelAndView() 方法就是做視圖解析的,我們來看一下該方法。
RequestMappingHandlerAdapter # getModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); // 是否已經完全處理了,若為 true,則直接返回 null // mavContainer.setRequestHandled(true) 已設置為 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; }
可以看到,該方法一開始就調用了 mavContainer.isRequestHandled() 方法,如果為 true,則返回 null,并進行下面的視圖解析。而 mavContainer.setRequestHandled(true) 方法已經將其設置為 true 了。這就是為什么加了 @ResponseBody 注解的方法不做視圖解析的原因。
@ResponseBody 注解即可加在方法中,也可以通過 @RestController 注解加在類上
類上添加了 @RestController 注解等效于為該類的所有方法上添加 @ResponseBody 注解
@ResponseBody 通過各種類型轉換器實現數據的轉換,如將數據轉換為 String、JSON、XML 等格式。并將數據寫入到 response body 中。而且它們使用的都是 UTF-8 編碼。
對于自定義的 Java 類轉換為 JSON 格式的數據,該類要是可序列化的。
使用了 @ResponseBody 注解標記的方法不再做視圖解
以上是“SpringMVC注解之@ResponseBody注解原理是什么”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。