Spring学习笔记_41——@RequestBody
@RequestBody
1. 介绍
@RequestBody
是 Spring 框架中用于处理 HTTP 请求的一个非常关键的注解。它主要用于将客户端发送的 HTTP 请求体中的 JSON、XML 或其他格式的数据转换到Java 方法参数上,这个转换过程通常需要一个消息转换器(Message Converter),如 MappingJackson2HttpMessageConverter
用于 JSON 数据的转换。
这个注解通常用在基于 REST 风格的 Web 服务开发中,特别是在处理 POST 和 PUT 请求时非常有用。
2. 注解细节
- 自动绑定:
- Spring 使用 HTTP 消息转换器(如
MappingJackson2HttpMessageConverter
用于 JSON)将请求体中的数据自动转换为 Java 对象。 - 转换过程依赖于请求的内容类型(
Content-Type
),如application/json
或application/xml
。
- Spring 使用 HTTP 消息转换器(如
- 注解位置:
@RequestBody
只能用于方法的参数上。- 不能用于类定义或方法返回类型上。
- 方法参数类型:
@RequestBody
通常与 POJO(Plain Old Java Object)一起使用,但也可以与 Map、List 等集合类型一起使用,只要这些类型能够由 HTTP 消息转换器正确解析。
- 自定义消息转换器:
- 如果需要处理特定的数据格式,可以自定义 HTTP 消息转换器,并注册到 Spring 的
WebMvcConfigurer
或WebFluxConfigurer
中。
- 如果需要处理特定的数据格式,可以自定义 HTTP 消息转换器,并注册到 Spring 的
- 异常处理:
- 如果请求体中的数据格式不正确或无法转换为指定的 Java 类型,Spring 会抛出一个
HttpMessageNotReadableException
异常。 - 可以使用
@ControllerAdvice
或@ExceptionHandler
来全局或局部处理这些异常。
- 如果请求体中的数据格式不正确或无法转换为指定的 Java 类型,Spring 会抛出一个
3. 场景
- 接收 JSON 数据:当客户端发送 JSON 格式的数据给服务器时,可以通过
@RequestBody
将这些数据自动映射到一个 Java 对象中。 - 接收 XML 数据:与 JSON 类似,如果客户端发送的是 XML 格式的数据,也可以通过配置相应的消息转换器来实现数据的自动映射。
- 接收其他格式的数据:只要存在对应的消息转换器,几乎可以支持任何类型的数据格式。
- POST 请求:客户端发送 JSON 或 XML 格式的数据到服务器,服务器使用
@RequestBody
将这些数据自动转换为 Java 对象。 - PUT 请求:与 POST 类似,用于更新资源,客户端发送的数据也会被
@RequestBody
转换。
4. 源码
/**
* @author Arjen Poutsma
* @since 3.0
* @see RequestHeader
* @see ResponseBody
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
// Spring3.2版本开始提供的boolean类型的属性
// 表示是否必须有请求体。
// true:必须有请求体
// false:可以没有请求体
// 如果为true时,未获取到请求体的数据时会强制报错。
// 默认值为true
boolean required() default true;
}
5. Demo
实体类
public class User {
private String username;
private String password;
// Getters and Setters
}
Controller类
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@PostMapping("/login")
public String login(@RequestBody User user) {
// 处理登录逻辑
return "Hello, " + user.getUsername();
}
}
6. 注意事项
- 数据验证:通常情况下,接收到的数据需要进行验证,确保数据的有效性。可以结合使用
@Valid
或@Validated
注解来实现数据校验。 - 内容类型:默认情况下,
@RequestBody
只接受application/json
类型的数据。如果需要支持其他类型,可以在@PostMapping
或@RequestMapping
中指定consumes
属性。 - 错误处理:如果数据转换失败或数据不符合预期格式,Spring 会抛出异常。可以通过配置全局异常处理器来统一处理这些异常。
7. 补充-消息转换器
在 Spring 框架中,消息转换器(Message Converters)是用于将 HTTP 请求和响应的消息体(即请求体和响应体)转换为 Java 对象,或者将 Java 对象转换为消息体的关键组件。它们在处理 @RequestBody
和 @ResponseBody
注解时起着至关重要的作用。
7.1 工作原理
- 请求处理:
- 当客户端发送一个 HTTP 请求时,请求体中的数据需要被转换为一个 Java 对象。Spring 会根据请求头中的
Content-Type
来选择合适的消息转换器。 - 例如,如果
Content-Type
是application/json
,Spring 会选择MappingJackson2HttpMessageConverter
来将 JSON 数据转换为 Java 对象。
- 当客户端发送一个 HTTP 请求时,请求体中的数据需要被转换为一个 Java 对象。Spring 会根据请求头中的
- 响应处理:
- 当控制器方法返回一个 Java 对象时,Spring 需要将这个对象转换为 HTTP 响应体。Spring 会根据响应头中的
Accept
来选择合适的消息转换器。 - 例如,如果
Accept
是application/json
,Spring 会选择MappingJackson2HttpMessageConverter
来将 Java 对象转换为 JSON 数据。
- 当控制器方法返回一个 Java 对象时,Spring 需要将这个对象转换为 HTTP 响应体。Spring 会根据响应头中的
7.2 内置消息转换器
Spring 提供了多个内置的消息转换器,常见的包括:
StringHttpMessageConverter
:- 用于处理纯文本数据,支持
text/plain
类型。
- 用于处理纯文本数据,支持
FormHttpMessageConverter
:- 用于处理表单数据,支持
application/x-www-form-urlencoded
和multipart/form-data
类型。
- 用于处理表单数据,支持
MappingJackson2HttpMessageConverter
:- 用于处理 JSON 数据,支持
application/json
类型。依赖于 Jackson 库。
- 用于处理 JSON 数据,支持
Jaxb2RootElementHttpMessageConverter
:- 用于处理 XML 数据,支持
application/xml
类型。依赖于 JAXB 库。
- 用于处理 XML 数据,支持
MappingJackson2XmlHttpMessageConverter
:- 用于处理 XML 数据,支持
application/xml
类型。依赖于 Jackson 的 XML 扩展库。
- 用于处理 XML 数据,支持
7.3 自定义消息转换器
如果内置的消息转换器不能满足需求,可以创建自定义的消息转换器。自定义消息转换器需要实现 HttpMessageConverter
接口。
消息转换器可以在 Spring 配置文件中或通过 Java 配置类进行配置。以下是一个示例,展示如何在 Java 配置类中添加自定义消息转换器:
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加自定义消息转换器
converters.add(new MyCustomMessageConverter());
// 配置默认的消息转换器
converters.add(new MappingJackson2HttpMessageConverter());
}
// 定义自定义消息转换器
public class MyCustomMessageConverter implements HttpMessageConverter<MyCustomType> {
// 实现 HttpMessageConverter 接口的方法
}
}
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MyCustomMessageConverter extends AbstractHttpMessageConverter<MyCustomType> {
private final JAXBContext jaxbContext;
public MyCustomMessageConverter() {
super(MediaType.APPLICATION_XML);
try {
this.jaxbContext = JAXBContext.newInstance(MyCustomType.class);
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext", e);
}
}
@Override
protected boolean supports(Class<?> clazz) {
return MyCustomType.class.isAssignableFrom(clazz);
}
@Override
protected MyCustomType readInternal(Class<? extends MyCustomType> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (MyCustomType) unmarshaller.unmarshal(inputMessage.getBody());
} catch (JAXBException e) {
throw new HttpMessageNotReadableException("Could not read XML: " + e.getMessage(), e, inputMessage);
}
}
@Override
protected void writeInternal(MyCustomType t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
try {
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(t, outputMessage.getBody());
} catch (JAXBException e) {
throw new HttpMessageNotWritableException("Could not write XML: " + e.getMessage(), e);
}
}
}
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
public class MyTextMessageConverter implements HttpMessageConverter<MyTextData> {
// 指定这个转换器支持的媒体类型
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return clazz.equals(MyTextData.class) && MediaType.TEXT_PLAIN.isCompatibleWith(mediaType);
}
// 指定这个转换器支持的媒体类型
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.equals(MyTextData.class) && MediaType.TEXT_PLAIN.isCompatibleWith(mediaType);
}
// 从输入消息中读取数据,转换为MyTextData对象
@Override
public MyTextData read(Class<? extends MyTextData> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String text = new InputStreamReader(inputMessage.getBody(), Charset.forName(inputMessage.getHeaders().getContentType().getCharset().name())).readLine();
return new MyTextData(text);
}
// 将MyTextData对象写入输出消息
@Override
public void write(MyTextData myTextData, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), Charset.forName(contentType.getCharset().name()));
writer.write(myTextData.getText());
writer.flush();
}
// 返回这个转换器支持的媒体类型列表
@Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(MediaType.TEXT_PLAIN);
}
}