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アノテーションについて説明しています。何か問題がある場合は、訂正してください。ありがとうございます。