@RequestBodyおよび@ResponseBodyアノテーション

1.フロンティア

この記事では、@ RequestBodyと@ResponseBodyの2つのアノテーションについて説明します。これら2つのアノテーションは、ほとんどすべてSpringMVCの開発で使用されます。ソースコード、ロール、アプリケーションシナリオなどから分析してみましょう。

二、@ RequestBody

2.1ソースコード

RequestBodyアノテーションのソースコードは次のとおりです。

// 注解使用在参数上
@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;

}

以下の結論は、ソースコードから導き出されたものです。

1)、RequestBodyアノテーションがリクエストパラメータで使用されます

2)RequestBodyでは、要求されたメッセージ本文にデフォルトでコンテンツが含まれている必要があります

2.2機能

RequestBodyアノテーションは、リクエストリクエストデータの本文部分読み取るために使用されます。最初にシステムのデフォルト設定HttpMessageConverter使用して解析し、次に解析されたオブジェクトデータをコントローラーのメソッドのパラメーターにバインドします。

例は次のとおりです。

2.3アプリケーションシナリオ

2.3.1。GET、POSTモードでリクエストする場合、リクエストヘッダーの値に従って判断しますContent-Type

1)、application / x-www-form-urlencoded(ブラウザーのネイティブフォーム送信)タイプはオプションです。この場合、データ@RequestParamおよび@ModelAttributeも処理できます。もちろん、@ RequestBodyも処理できます。

2)、multipart / form-data(ネイティブフォームにはファイルアップロードが含まれています)タイプは処理できません(@RequestBodyはこの形式のデータを処理できません) 

3)application / jsonやapplication / xmlタイプなどの他の形式の場合、処理には@RequestBodyを使用する必要があります

2.3.2。PUTモードで送信(リクエスト)する場合は、リクエストヘッダーの値で判断してくださいContent-Type

1)、application / x-www-form-urlencoded(ブラウザーのネイティブフォーム送信)タイプを処理できます

2)、multipart / form-data(元のフォームにはファイルがアップロードされています)タイプは処理できません

3)、他のフォーマットタイプも処理できます

3、@ ResponseBody

3.1ソースコード

ResponseBodyアノテーションのソースコードは次のとおりです。

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

}

ソースコードから次の結論が導き出されます。ResponseBodyアノテーションはクラスまたはメソッドで使用されます

3.2機能

ResponseBodyアノテーションは、コントローラーメソッドによって返されたオブジェクトをHttpMessageConverterを介して指定された形式に変換し、それをResponseオブジェクトの本文データ領域に書き込むために使用されます

例は次のとおりです。

3.3アプリケーションシナリオ

ajaxがデータを要求したときに返されるデータで主に使用されます。返されるデータはhtmlページではなく、他の形式(json、xmlなど)のデータです。

上記の分析から、RequestBodyとResponseBodyの両方がHttpMessageConverter(httpメッセージコンバーター)を使用してデータ形式を変換していることがわかります。HttpMessageConverterのソースコードを分析してみましょう。

4、HttpMessageConverter 

4.1HttpMessageConverterソースコード

HttpMessageConverterインターフェイスのソースコードは次のとおりです。

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;

}

このインターフェイスクラスには、データの読み取り時にcanReadと読み取り、データの書き込み時にcanWriteと書き込みの4つのメソッドが定義されています。

<mvc:annotation-driven>タグ構成を使用する場合、RequestMappingHandlerAdapterがデフォルトで構成され、いくつかのデフォルトのHttpMessageConverterがRequestMappingHandlerAdapterで構成されます。spring-mvc.xsdの定義は次のとおりです。

次の図に、RequestMappingHandlerAdapterによって定義された特定のHttpMessageConverterを示します。

上の図のこれらのHttpMessageConvertersの特定の関数は次のとおりです。

1)、ByteArrayHttpMessageConverterは  、バイナリ配列形式でデータを読み書きするために使用されます

2)StringHttpMessageConverterは  、文字列形式でデータを読み取り、バイナリ形式でデータを書き込むために使用されます

3)、ResourceHttpMessageConverterはリソースファイルデータの読み取りと書き込みに使用されます

4)、SourceHttpMessageConverterは 、xmlでjava.xml.transform.Sourceによって定義されたデータの読み取りと書き込みに使用されます

5)。FormHttpMessageConverterは 、フォームによって送信されたデータを読み取るために使用されます。multipart / form-data形式のデータではなくapplication / x-www-form-urlencoded形式を読み取ることができ、フォームにapplication / x-www-データを書き込むことができます。 -urlencodedおよびmultipart / form-data形式

6)、Jaxb2RootElementHttpMessageConverterは  、xmlタグ形式でデータを読み書きするために使用されます

7)、MappingJacksonHttpMessageConverterは  、json形式でデータを読み書きするために使用されます

8)、AtomFeedHttpMessageConverterは、Atom形式でデータを読み書きするために使用されます

9)、RssChannelHttpMessageConverterは  、Rss形式でデータを読み書きするために使用されます

RequestBodyアノテーションとResponseBodyアノテーションを使用する場合、RequestMappingHandlerAdapterによって要求された対応するアダプターは、状況に応じて、上記のコンバーターを使用して対応する形式でデータを読み書きすることを決定します。

4.2HttpMessageConverterマッチングプロセス

4.2.1、RequestBodyアノテーション

リクエストオブジェクトのヘッダー部分からContent-Typeタイプを取得し、適切なHttpMessageConverterを1つずつ照合してデータを読み取ります

エントリコードは、AbstractMessageConverterMethodArgumentResolverのreadWithMessageConvertersメソッドにあります。ソースコードは次のとおりです。

	/**
	 * 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アノテーション

応答ヘッダーからcontentType属性値を取得するか、要求オブジェクトのヘッダー部分からAccept属性(コンマ区切り)を取得し、戻り値に対応するメディアタイプを1つずつトラバースして、処理できるHttpMessageConverterを見つけます。処理するものを見つけてから戻る

 

エントリコードは、AbstractMessageConverterMethodProcessorのwriteWithMessageConvertersメソッドにあります。ソースコードは次のとおりです。

	/**
	 * 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);
		}
	}

五数要約

ここでは、RequestBodyアノテーションとResponseBodyアノテーションについて説明しています。何か問題がある場合は、訂正してください。ありがとうございます。

 

おすすめ

転載: blog.csdn.net/ywlmsm1224811/article/details/103201640