Bootstrap

Spring学习笔记_41——@RequestBody

@RequestBody

1. 介绍

@RequestBody 是 Spring 框架中用于处理 HTTP 请求的一个非常关键的注解。它主要用于将客户端发送的 HTTP 请求体中的 JSON、XML 或其他格式的数据转换到Java 方法参数上,这个转换过程通常需要一个消息转换器(Message Converter),如 MappingJackson2HttpMessageConverter 用于 JSON 数据的转换。

这个注解通常用在基于 REST 风格的 Web 服务开发中,特别是在处理 POST 和 PUT 请求时非常有用。

2. 注解细节

  1. 自动绑定:
    • Spring 使用 HTTP 消息转换器(如 MappingJackson2HttpMessageConverter 用于 JSON)将请求体中的数据自动转换为 Java 对象。
    • 转换过程依赖于请求的内容类型(Content-Type),如 application/jsonapplication/xml
  2. 注解位置:
    • @RequestBody 只能用于方法的参数上。
    • 不能用于类定义或方法返回类型上。
  3. 方法参数类型:
    • @RequestBody 通常与 POJO(Plain Old Java Object)一起使用,但也可以与 Map、List 等集合类型一起使用,只要这些类型能够由 HTTP 消息转换器正确解析。
  4. 自定义消息转换器:
    • 如果需要处理特定的数据格式,可以自定义 HTTP 消息转换器,并注册到 Spring 的 WebMvcConfigurerWebFluxConfigurer 中。
  5. 异常处理:
    • 如果请求体中的数据格式不正确或无法转换为指定的 Java 类型,Spring 会抛出一个 HttpMessageNotReadableException 异常。
    • 可以使用 @ControllerAdvice@ExceptionHandler 来全局或局部处理这些异常。

3. 场景

  1. 接收 JSON 数据:当客户端发送 JSON 格式的数据给服务器时,可以通过 @RequestBody 将这些数据自动映射到一个 Java 对象中。
  2. 接收 XML 数据:与 JSON 类似,如果客户端发送的是 XML 格式的数据,也可以通过配置相应的消息转换器来实现数据的自动映射。
  3. 接收其他格式的数据:只要存在对应的消息转换器,几乎可以支持任何类型的数据格式。

  1. POST 请求:客户端发送 JSON 或 XML 格式的数据到服务器,服务器使用 @RequestBody 将这些数据自动转换为 Java 对象。
  2. 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 工作原理
  1. 请求处理
    • 当客户端发送一个 HTTP 请求时,请求体中的数据需要被转换为一个 Java 对象。Spring 会根据请求头中的 Content-Type 来选择合适的消息转换器。
    • 例如,如果 Content-Typeapplication/json,Spring 会选择 MappingJackson2HttpMessageConverter 来将 JSON 数据转换为 Java 对象。
  2. 响应处理
    • 当控制器方法返回一个 Java 对象时,Spring 需要将这个对象转换为 HTTP 响应体。Spring 会根据响应头中的 Accept 来选择合适的消息转换器。
    • 例如,如果 Acceptapplication/json,Spring 会选择 MappingJackson2HttpMessageConverter 来将 Java 对象转换为 JSON 数据。
7.2 内置消息转换器

Spring 提供了多个内置的消息转换器,常见的包括:

  1. StringHttpMessageConverter
    • 用于处理纯文本数据,支持 text/plain 类型。
  2. FormHttpMessageConverter
    • 用于处理表单数据,支持 application/x-www-form-urlencodedmultipart/form-data 类型。
  3. MappingJackson2HttpMessageConverter
    • 用于处理 JSON 数据,支持 application/json 类型。依赖于 Jackson 库。
  4. Jaxb2RootElementHttpMessageConverter
    • 用于处理 XML 数据,支持 application/xml 类型。依赖于 JAXB 库。
  5. MappingJackson2XmlHttpMessageConverter
    • 用于处理 XML 数据,支持 application/xml 类型。依赖于 Jackson 的 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);
    }
}
;