国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術(shù)文章
文章詳情頁

SpringMVC注解之@ResponseBody注解原理

瀏覽:89日期:2022-08-11 17:04:20
目錄一、介紹二、作用范圍三、源碼分析四、總結(jié)一、介紹 @ResponseBody 注解的作用是將方法的返回值通過適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,寫入到 response 對象的 body 區(qū),通常用來返回 JSON、XML 數(shù)據(jù)。 使用了 @ResponseBody 注解標(biāo)記的方法不再做視圖解析二、作用范圍 標(biāo)記在方法上 標(biāo)記在類上

通過 @RestController 注解實(shí)現(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.classif (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());}// 返回值類型是否是實(shí)現(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 中g(shù)enericConverter.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/jsonaddDefaultHeaders(headers, t, contentType);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {@Overridepublic OutputStream getBody() {return outputStream;}@Overridepublic 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/jsonMediaType contentType = outputMessage.getHeaders().getContentType();// 獲取 JSON 數(shù)據(jù)的編碼為 UTF-8JsonEncoding 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);}// 用于操作可序列化對象的類// 我們自定義的類一定要實(shí)現(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)實(shí)現(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,并進(jìn)行下面的視圖解析。而 mavContainer.setRequestHandled(true) 方法已經(jīng)將其設(shè)置為 true 了。這就是為什么加了 @ResponseBody 注解的方法不做視圖解析的原因。

四、總結(jié) @ResponseBody 注解即可加在方法中,也可以通過 @RestController 注解加在類上 類上添加了 @RestController 注解等效于為該類的所有方法上添加 @ResponseBody 注解 @ResponseBody 通過各種類型轉(zhuǎn)換器實(shí)現(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)記的方法不再做視圖解

到此這篇關(guān)于Java源碼解析之@ResponseBody注解原理的文章就介紹到這了,更多相關(guān)@ResponseBody注解原理內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 国产欧美日韩一区二区三区在线 | 日本精品一区二区三区视频 | 成人a一级毛片免费看 | 欧美无玛 | 美女网站在线观看视频18 | 成人毛片免费免费 | 国产uv1区二区三区 国产va免费精品高清在线观看 | 怡红院视频在线 | 99精品久久久久久久免费看蜜月 | 精品国产91久久久久 | 成网站在线观看人免费 | www.久久视频 | 波多野结衣在线视频免费观看 | 免费观看欧美精品成人毛片 | 91精品国产综合久久欧美 | 日本天堂网址 | 欧美高清日韩 | 99精品视频在线视频免费观看 | 美女曰皮| 中文字幕在线永久 | 日本一区二区三区在线 视频观看免费 | 性刺激免费视频观看在线观看 | 九九九国产 | 国产成人一区二区三区精品久久 | 最新国产三级在线观看不卡 | 国产精品一区二区三区四区五区 | 在线观看亚洲欧美 | 亚洲成a人片在线播放 | 男女乱淫视频 | 国产精品11p | 在线综合+亚洲+欧美中文字幕 | 欧美激情视频在线观看一区二区三区 | 亚洲欧美视频一区 | 毛片亚洲毛片亚洲毛片 | 久久久久久久久网站 | 欧美高清强视频 | a毛片视频免费观看影院 | 亚洲精品高清国产一线久久97 | 毛片爽爽爽免费看 | 欧美一及片 | 亚洲成人91|