@RequestBody and @ResponseBody annotations

1. Frontier

In this article, we explain the two annotations @RequestBody and @ResponseBody. These two annotations are almost all used in the development of SpringMVC. Let's analyze them from their source code, function, application scenarios, etc.

二、@RequestBody

2.1 Source code

The source code of RequestBody annotation is as follows:

// 注解使用在参数上
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {

	/**
	 * Whether body content is required.
	 * <p>Default is {@code true}, leading to an exception thrown in case
	 * there is no body content. Switch this to {@code false} if you prefer
	 * {@code null} to be passed when the body content is {@code null}.
	 * @since 3.2
	 */
	// 默认true,即要求请求的消息体 body 内容不能为空
	boolean required() default true;

}

The following conclusions are drawn from the source code:

1), RequestBody annotation is used on request parameters

2) RequestBody requires that the requested message body must have content by default

2.2 Function

The RequestBody annotation is used to read the body part data of the Request request , first use the HttpMessageConverter configured by the system to parse it, and then bind the parsed object data to the method parameters in the controller

The example is as follows:

2.3 Application scenarios

2.3.1. When requesting in GET, POST mode, judge according to the value of request header Content-Type

1), application/x-www-form-urlencoded (browser's native form submission) type is optional, because in this case the data @RequestParam and @ModelAttribute can also be processed, of course @RequestBody can also be processed

2), multipart/form-data (the native form contains file upload) type cannot be processed (@RequestBody cannot process data in this format) 

3) For other formats, such as application/json or application/xml type, @RequestBody must be used to process

2.3.2. When submitting (request) in PUT mode, judge according to the value of request header Content-Type

1), application/x-www-form-urlencoded (browser's native form submission) type can be processed

2), multipart/form-data (the original form has a file upload) type cannot be processed

3), other format types can also be processed

Three, @ResponseBody

3.1 Source code

The source code of ResponseBody annotation is as follows:

// 注解使用在类上或者方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {

}

The following conclusions are drawn from the source code: ResponseBody annotations are used on classes or methods

3.2 Function

The ResponseBody annotation is used to convert the object returned by the controller method to the specified format through HttpMessageConverter, and then write it to the body data area of ​​the Response object

The example is as follows:

3.3 Application scenarios

Mainly used in the data returned when ajax requests data. The returned data is not html page but data in some other format (such as json, xml, etc.)

From the above analysis, we can see that RequestBody and ResponseBody both use HttpMessageConverter (http message converter) to convert the data format. Let’s analyze the source code of HttpMessageConverter.

Four, HttpMessageConverter 

4.1 HttpMessageConverter source code

The source code of the HttpMessageConverter interface is as follows:

public interface HttpMessageConverter<T> {

	/**
	 * Indicates whether the given class can be read by this converter.
	 * @param clazz the class to test for readability
	 * @param mediaType the media type to read (can be {@code null} if not specified);
	 * typically the value of a {@code Content-Type} header.
	 * @return {@code true} if readable; {@code false} otherwise
	 */
	// 是否可以读取指定的类
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Indicates whether the given class can be written by this converter.
	 * @param clazz the class to test for writability
	 * @param mediaType the media type to write (can be {@code null} if not specified);
	 * typically the value of an {@code Accept} header.
	 * @return {@code true} if writable; {@code false} otherwise
	 */
	// 是否可以写入指定的类
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Return the list of {@link MediaType} objects supported by this converter.
	 * @return the list of supported media types
	 */
	// 获取支持的媒体类型列表
	List<MediaType> getSupportedMediaTypes();

	/**
	 * Read an object of the given type from the given input message, and returns it.
	 * @param clazz the type of object to return. This type must have previously been passed to the
	 * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
	 * @param inputMessage the HTTP input message to read from
	 * @return the converted object
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotReadableException in case of conversion errors
	 */
	// 从给定的输入消息中读取给定类型的对象,并返回它
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	/**
	 * Write an given object to the given output message.
	 * @param t the object to write to the output message. The type of this object must have previously been
	 * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
	 * @param contentType the content type to use when writing. May be {@code null} to indicate that the
	 * default content type of the converter must be used. If not {@code null}, this media type must have
	 * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
	 * returned {@code true}.
	 * @param outputMessage the message to write to
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotWritableException in case of conversion errors
	 */
	// 将给定的对象写入给定的输出消息
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

Four methods are defined in this interface class, which are canRead and read when reading data and canWrite and write when writing data.

When using the <mvc:annotation-driven> tag configuration, RequestMappingHandlerAdapter is configured by default, and some default HttpMessageConverter is configured in RequestMappingHandlerAdapter. The definition of spring-mvc.xsd is as follows:

The specific HttpMessageConverter defined by RequestMappingHandlerAdapter is shown in the following figure:

The specific functions of these HttpMessageConverters in the above figure are as follows:

1), ByteArrayHttpMessageConverter is  used to read and write data in binary array format

2) StringHttpMessageConverter is  used to read data in string format and write data in binary format

3), ResourceHttpMessageConverter is used to read and write resource file data

4), SourceHttpMessageConverter is  used to read and write data defined by java.xml.transform.Source in xml

5). FormHttpMessageConverter is  used to read the data submitted by the form. It can read application/x-www-form-urlencoded format, not multipart/form-data format data, and can write application/x-www- Data in form-urlencoded and multipart/form-data formats

6), Jaxb2RootElementHttpMessageConverter is  used to read and write data in xml tag format

7), MappingJacksonHttpMessageConverter is  used to read and write data in json format

8), AtomFeedHttpMessageConverter is used to read and write data in Atom format

9), RssChannelHttpMessageConverter is  used to read and write data in Rss format

When we use RequestBody and ResponseBody annotations, the corresponding adapter requested by RequestMappingHandlerAdapter will decide to use the above converter to read and write data in the corresponding format according to the situation.

4.2 HttpMessageConverter matching process

4.2.1, RequestBody annotation

Get the Content-Type type from the header part of the request object, and match the appropriate HttpMessageConverter one by one to read the data

The entry code is in the readWithMessageConverters method of AbstractMessageConverterMethodArgumentResolver. The source code is as follows:

	/**
	 * Create the method argument value of the expected parameter type by
	 * reading from the given request.
	 * @param <T> the expected type of the argument value to be created
	 * @param webRequest the current request
	 * @param parameter the method parameter descriptor (may be {@code null})
	 * @param paramType the type of the argument value to be created
	 * @return the created method argument value
	 * @throws IOException if the reading from the request fails
	 * @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
	 */
	// 从请求中读取内容并转换成期望的数据类型
	@Nullable
	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
			Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		// NativeWebRequest 转换成 ServletServerHttpRequest 对象
		HttpInputMessage inputMessage = createInputMessage(webRequest);
		// 根据请求使用具体的消息=转换器读取数据
		return readWithMessageConverters(inputMessage, parameter, paramType);
	}

	/**
	 * Create the method argument value of the expected parameter type by reading
	 * from the given HttpInputMessage.
	 * @param <T> the expected type of the argument value to be created
	 * @param inputMessage the HTTP input message representing the current request
	 * @param parameter the method parameter descriptor
	 * @param targetType the target type, not necessarily the same as the method
	 * parameter type, e.g. for {@code HttpEntity<String>}.
	 * @return the created method argument value
	 * @throws IOException if the reading from the request fails
	 * @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
	 */
	@SuppressWarnings("unchecked")
	@Nullable
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		MediaType contentType;
		boolean noContentType = false;
		try {
			// 从 request 的 header 中获取 ContentType 值
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
			noContentType = true;
			// 无 contentType 值时,默认设置为 application/octet-stream
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}

		Class<?> contextClass = parameter.getContainingClass();
		Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class<T>) resolvableType.resolve();
		}

		// 获取请求方法,例如 GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message;
		try {
			// request 的 body 内容读取以及校验是否为空
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

			// 循环遍历所有的 HttpMessageConverter 消息转换器
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				// 调用 canRead 方法过滤出匹配对应的 HttpMessageConverter 转换器
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					// request 的 body 有数据时
					if (message.hasBody()) {
						// 读取 request 的 body 数据的前置处理
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						// 使用具体的 converter 对request 的 body 数据读取并转换
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						// 读取 request 的 body 数据的后置处理
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}

		if (body == NO_VALUE) {
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
				return null;
			}
			throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
		}

		MediaType selectedContentType = contentType;
		Object theBody = body;
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
			return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
		});

		return body;
	}

4.2.2, ResponseBody annotation

Get the contentType attribute value from the response header or get the Accept attribute (comma separated) from the header part of the request object, and traverse the media type corresponding to the return value one by one to find the HttpMessageConverter that can be processed, and return after finding one for processing

 

The entry code is in the writeWithMessageConverters method of AbstractMessageConverterMethodProcessor. The source code is as follows:

	/**
	 * Writes the given return value to the given web request. Delegates to
	 * {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
	 */
	// 将返回值处理成需要的数据类型
	protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		// 创建 ServletServerHttpRequest
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		// 创建 ServletServerHttpResponse
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
		// 将返回值转成需要的数据类型
		writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
	}

	/**
	 * Writes the given return type to the given output message.
	 * @param value the value to write to the output message
	 * @param returnType the type of the value
	 * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
	 * @param outputMessage the output message to write to
	 * @throws IOException thrown in case of I/O errors
	 * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated
	 * by the {@code Accept} header on the request cannot be met by the message converters
	 */
	@SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {
			// 返回值是字符串类型
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			// 获取返回值的类型
			valueType = getReturnValueType(body, returnType);
			// 需要转换成的数据类型
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		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;
		// 从 Response 的 header 中获取 contentType 属性值
		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			// Response 的 header 中的 contentType 属性指定了返回值类型
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
			selectedMediaType = contentType;
		}
		else {
			// Response 的 header 中的 contentType 没有指定返回值类型
			HttpServletRequest request = inputMessage.getServletRequest();
			// 从 request 中获取可以接受的媒体类型
			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);

			// 遍历 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();
			// 匹配到一个 HttpMessageConverter 消息转换器即可,处理后 body 数据后即可返回
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				// 调用 canWrite 方法匹配出返回值媒体类型对应的 HttpMessageConverter 消息转换器
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					// body 内容写入前的前置处理
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						// body 内容不为空时,HttpMessageConverter 消息转换器处理内容
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						// response 的 header 中增加 Content-Disposition 属性内容
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							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) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

Five, summary

The RequestBody annotation and ResponseBody annotation are explained here. If there is something wrong, please correct me, thank you.

 

Guess you like

Origin blog.csdn.net/ywlmsm1224811/article/details/103201640