ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

@RequestBody、@ResponseBody的具体用法和使用时机

@RequestBody、@ResponseBody的具体用法和使用时机 摘要本文深入解析 Spring MVC 中RequestBody和ResponseBody注解的核心原理与使用场景详细阐述 HttpMessageConverter 的工作机制及其在请求响应数据转换中的关键作用。通过源码分析和实际应用示例帮助开发者理解 Spring 如何处理 HTTP 请求体与响应体的数据绑定与转换。一、引言在上一篇文章中我们详细介绍了RequestMapping的方法参数绑定机制。本文将深入探讨RequestBody和ResponseBody这两个注解的具体用法、适用场景以及背后的 HttpMessageConverter 工作机制。二、RequestBody 注解详解2.1 作用RequestBody注解主要用于处理 HTTP 请求体中的数据读取 HTTP Request 的 body 部分数据使用系统配置的 HttpMessageConverter 进行解析将解析后的数据绑定到 Controller 方法的参数上2.2 使用时机GET/POST 请求根据请求头 Content-Type 的值判断application/x-www-form-urlencoded可选非必须因为RequestParam、ModelAttribute也能处理multipart/form-data不能处理RequestBody不支持此格式其他格式如 application/json、application/xml必须使用RequestBodyPUT 请求根据请求头 Content-Type 的值判断application/x-www-form-urlencoded必须使用RequestBodymultipart/form-data不能处理其他格式必须使用RequestBody说明请求体数据的编码格式由请求头中的 Content-Type 指定。三、ResponseBody 注解详解3.1 作用ResponseBody注解用于将 Controller 方法返回的对象通过合适的 HttpMessageConverter 转换为指定格式后写入到 Response 对象的 body 数据区。3.2 使用时机当返回的数据不是 HTML 页面而是其他格式如 JSON、XML 等时使用。四、HttpMessageConverter 核心机制4.1 接口定义HttpMessageConverter 是 Spring MVC 中处理 HTTP 请求和响应数据转换的策略接口/** * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. * * author Arjen Poutsma * author Juergen Hoeller * since 3.0 */ public interface HttpMessageConverterT { /** * 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(Classlt;?gt; clazz, 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(Classlt;?gt; clazz, MediaType mediaType); /** Return the list of {link MediaType} objects supported by this converter. return the list of supported media types */ Listlt;MediaTypegt; getSupportedMediaTypes(); /** Read an object of the given type form 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(Classlt;? extends Tgt; 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, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }该接口定义了四个核心方法canRead()和read()用于读取数据canWrite()和write()用于写入数据4.2 默认配置的转换器在使用mvc:annotation-driven /标签配置时Spring 会默认配置 RequestMappingHandlerAdapter并为其配置以下 HttpMessageConverterByteArrayHttpMessageConverter converts byte arrays. StringHttpMessageConverter converts strings. ResourceHttpMessageConverter converts to/from org.springframework.core.io.Resource for all media types. SourceHttpMessageConverter converts to/from a javax.xml.transform.Source. FormHttpMessageConverter converts form data to/from a MultiValueMapString, String. Jaxb2RootElementHttpMessageConverter converts Java objects to/from XML — added if JAXB2 is present on the classpath. MappingJacksonHttpMessageConverter converts to/from JSON — added if Jackson is present on the classpath.各转换器的具体功能ByteArrayHttpMessageConverter负责读取和写入二进制格式数据StringHttpMessageConverter负责读取和写入字符串格式数据ResourceHttpMessageConverter负责读取和写入资源文件数据FormHttpMessageConverter负责读取 form 提交的数据支持 application/x-www-form-urlencoded不支持 multipart/form-dataMappingJacksonHttpMessageConverter负责读取和写入 JSON 格式数据SourceHttpMessageConverter负责读取和写入 javax.xml.transform.Source 定义的 XML 数据Jaxb2RootElementHttpMessageConverter负责读取和写入 XML 标签格式数据AtomFeedHttpMessageConverter负责读取和写入 Atom 格式数据RssChannelHttpMessageConverter负责读取和写入 RSS 格式数据当使用RequestBody和ResponseBody注解时RequestMappingHandlerAdapter 会使用这些转换器进行数据读取或写入。五、HttpMessageConverter 匹配过程5.1 RequestBody 注解的匹配过程根据 Request 对象 header 部分的 Content-Type 类型逐一匹配合适的 HttpMessageConverter 来读取数据。Spring 3.1 源码示例private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class? paramType) throws Exception { // 1. 获取 Content-Type若缺失则直接抛出异常避免后续无效计算 MediaType contentType inputMessage.getHeaders().getContentType(); if (contentType null) { throw new HttpMediaTypeNotSupportedException( Cannot extract parameter ( buildParameterDescription(methodParam) ): no Content-Type found); } // 2. 遍历消息转换器寻找第一个支持读取该类型和媒体类型的转换器 if (this.messageConverters ! null) { for (HttpMessageConverter? messageConverter : this.messageConverters) { if (messageConverter.canRead(paramType, contentType)) { if (logger.isDebugEnabled()) { logger.debug(Reading [{}] as \{}\ using [{}], paramType.getName(), contentType, messageConverter); } return messageConverter.read(paramType, inputMessage); } } } // 3. 如果没有找到合适的转换器收集所有支持的媒体类型并抛出异常 // 注意只有在失败时才执行此开销较大的操作 ListMediaType allSupportedMediaTypes collectAllSupportedMediaTypes(); throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); } /** * 构建参数描述信息用于异常消息 */ private String buildParameterDescription(MethodParameter methodParam) { StringBuilder builder new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType())); String paramName methodParam.getParameterName(); if (paramName ! null) { builder.append( ).append(paramName); } return builder.toString(); } /** * 收集所有消息转换器支持的媒体类型 */ private ListMediaType collectAllSupportedMediaTypes() { ListMediaType allSupportedMediaTypes new ArrayList(); if (this.messageConverters ! null) { for (HttpMessageConverter? messageConverter : this.messageConverters) { allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); } } return allSupportedMediaTypes; }5.2 ResponseBody 注解的匹配过程根据 Request 对象 header 部分的 Accept 属性逗号分隔逐一按 accept 中的类型遍历找到能处理的 HttpMessageConverter。源码示例private void writeWithMessageConverters(Object returnValue, HttpInputMessage inputMessage, HttpOutputMessage outputMessage) throws IOException, HttpMediaTypeNotAcceptableException { // 1. 获取并预处理客户端接受的媒体类型 (Accept Header) ListMediaType acceptedMediaTypes inputMessage.getHeaders().getAccept(); if (acceptedMediaTypes.isEmpty()) { acceptedMediaTypes Collections.singletonList(MediaType.ALL); } else { // 只有在非空且非单元素时才排序避免不必要的开销 // MediaType.sortByQualityValue 会修改原列表确保它是可变的或创建副本 // 注意Spring 内部通常使用 ArrayList这里假设传入的是可变列表或已处理 MediaType.sortByQualityValue(acceptedMediaTypes); } Class? returnValueType returnValue.getClass(); // 2. 遍历 Accept 头中的媒体类型寻找第一个支持写入的转换器 if (getMessageConverters() ! null) { for (MediaType acceptedMediaType : acceptedMediaTypes) { for (HttpMessageConverter? messageConverter : getMessageConverters()) { if (messageConverter.canWrite(returnValueType, acceptedMediaType)) { // 执行写入操作 messageConverter.write(returnValue, acceptedMediaType, outputMessage); // 记录调试日志 if (logger.isDebugEnabled()) { MediaType contentType outputMessage.getHeaders().getContentType(); if (contentType null) { contentType acceptedMediaType; } logger.debug(Written [{}] as \{}\ using [{}], returnValue, contentType, messageConverter); } this.responseArgumentUsed true; return; // 成功写入直接返回 } } } } // 3. 如果循环结束仍未找到合适的转换器收集所有支持的类型并抛出异常 // 优化点仅在失败路径上执行此高开销操作 ListMediaType allSupportedMediaTypes collectAllSupportedMediaTypes(); throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes); } /** * 辅助方法收集所有消息转换器支持的媒体类型 * 仅在异常路径调用避免正常路径的性能损耗 */ private ListMediaType collectAllSupportedMediaTypes() { ListMediaType allSupportedMediaTypes new ArrayList(); if (getMessageConverters() ! null) { for (HttpMessageConverter? messageConverter : getMessageConverters()) { allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); } } return allSupportedMediaTypes; }六、补充说明MappingJacksonHttpMessageConverter调用objectMapper.writeValue(OutputStream stream, Object)方法使用ResponseBody注解返回的对象会传入 Object 参数。如果返回的对象已经是格式化好的 JSON 字符串不应该使用ResponseBody注解而应该这样处理// 1. 设置响应内容类型 response.setContentType(application/json; charsetUTF-8); // 2. 直接输出 JSON 字符串 response.getWriter().print(jsonStr);这样可以直接将 JSON 字符串输出到响应体视图返回类型为 void。七、总结RequestBody和ResponseBody是 Spring MVC 中处理非表单数据绑定的核心注解它们通过 HttpMessageConverter 机制实现了请求体与响应体的自动转换。理解这些注解的工作原理和 HttpMessageConverter 的匹配过程有助于开发者更好地处理 RESTful API 中的数据交互。
返回列表