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 注解标记的方法不再做视图解
到此这篇关于Java源码解析之@ResponseBody注解原理的文章就介绍到这了,更多相关@ResponseBody注解原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!