一、前沿
本文中我们讲解 @RequestBody 和 @ResponseBody 这两个注解,这两个注解在 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 默认要求请求的消息体 body 必须有内容
2.2 作用
RequestBody 注解用于读取 Request 请求的 body 部分数据,先使用系统默认配置的 HttpMessageConverter 进行解析,然后把解析好的对象数据绑定到 controller 中方法的参数上
示例如下图:
2.3 应用场景
2.3.1、GET、POST方式请求时,根据 request header Content-Type 的值来判断
1)、application/x-www-form-urlencoded (浏览器的原生 form 表单提交)类型时为可选,因为这种情况下的数据 @RequestParam 和 @ModelAttribute 也可以处理,当然 @RequestBody 也能处理
2)、multipart/form-data(原生 form 表单包含有文件上传) 类型时不能处理(@RequestBody 不能处理这种格式的数据)
3)、其他格式,比如 application/json 或者 application/xml 类型时,必须使用 @RequestBody 来处理
2.3.2、PUT方式提交(请求)时,根据 request header Content-Type 的值来判断
1)、application/x-www-form-urlencoded (浏览器的原生 form 表单提交)类型时可以处理
2)、multipart/form-data(原生 form 表单有文件上传) 类型时不能处理
3)、其他格式类型也是可以处理
三、@ResponseBody
3.1 源码
ResponseBody 注解的源码如下:
// 注解使用在类上或者方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
源码中得出以下结论: ResponseBody 注解使用在类上或者方法上
3.2 作用
ResponseBody 注解用于将 controller 的方法返回的对象通过 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区
示例如下图:
3.3 应用场景
主要应用于 ajax 请求数据时的返回数据,返回的数据不是html页面,而是其他某种格式的数据时(例如 json、xml等)
通过以上分析可知,RequestBody 和 ResponseBody 都是通过 HttpMessageConverter(http 消息转换器) 转换数据格式,下面我们分析一下 HttpMessageConverter 源码
四、HttpMessageConverter
4.1 HttpMessageConverter 源码
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、read 和写入数据时的 canWrite、write
在使用 <mvc:annotation-driven> 标签配置时,默认配置了 RequestMappingHandlerAdapter,而在 RequestMappingHandlerAdapter 中配置了一些默认的 HttpMessageConverter,spring-mvc.xsd定义内容如下:
RequestMappingHandlerAdapter 定义的具体 HttpMessageConverter 如下图所示:
上图中这些 HttpMessageConverter 具体作用如下:
1)、ByteArrayHttpMessageConverter 用于读写二进制数组格式的数据
2)、StringHttpMessageConverter 用于读取字符串格式的数据和写出二进制格式的数据
3)、ResourceHttpMessageConverter 用于读写资源文件数据
4)、SourceHttpMessageConverter 用于读写xml中的 java.xml.transform.Source 定义的数据
5)、FormHttpMessageConverter 用于读取form表单提交的数据,能读取 application/x-www-form-urlencoded 格式的,不能读取 multipart/form-data 格式的数据,能写入application/x-www-form-urlencoded 和 multipart/form-data 格式的数据
6)、Jaxb2RootElementHttpMessageConverter 用于读写xml标签格式的数据
7)、MappingJacksonHttpMessageConverter 用于读写json格式的数据
8)、AtomFeedHttpMessageConverter 用于读写Atom格式的数据
9)、RssChannelHttpMessageConverter 用于读写Rss格式的数据
当我们使用 RequestBody 和 ResponseBody 注解的时候,RequestMappingHandlerAdapter 请求对应的适配器就会根据情况决定使用上述的转换器来读取和写入相应格式的数据
4.2 HttpMessageConverter 匹配过程
4.2.1、 RequestBody 注解
从 request 对象 header 部分获取 Content-Type 类型,逐一匹配合适的 HttpMessageConverter 来读取数据
入口代码在 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 注解
从 response 的 header 中获取 contentType 属性值 或者 从 request 对象 header 部分获取 Accept 属性(逗号分隔),逐一按返回值对应的媒体类型去遍历找到能处理的 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 注解就讲解完了,若有不当之处,请指正,谢谢。