关于Spring @RequestBody 自动映射模型

    在很多时候,Spring的注解为我们提供了很多方便,但只知道其用法,不懂其执行原理,有时候出错了,很难快速的定位出错原因,今天我想把自己对于@Requestbody这个注解的一点想法和大家分享下。

   首先Spring处理一个请求时,请求的入口就是大家在配置文件中配置的 DispathcherServlet 这分发类,其实这个类能够接受到request的原理就是它实现了Servlet的doGet,doPost等方法,在没有正式达到Controller代码时,在它处理逻辑时,会获取Controller的反射实例,通过反射实例获取它的注解参数,执行完注解方法后,才会返回到Controller中,所以配置了@Requeat,@Valid 等注解时,返回到Controller中的都是已经经过数据绑定和校验后的对象,当Controller配置@Requestbody这个注解时,Spring会调用  AbstractMessageConverterMethodArgumentResolver 这个父类的 readWithMessageConverters 方法 通过 HttpMessageConverter类来进行解析,然后把数据要返回的对象上,再把绑定后的对象返回到Controller.

 

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
      MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {

   MediaType contentType;
   try {
      contentType = inputMessage.getHeaders().getContentType();
   }
   catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotSupportedException(ex.getMessage());
   }
   if (contentType == null) {
      contentType = MediaType.APPLICATION_OCTET_STREAM;
   }

   Class<?> contextClass = methodParam.getContainingClass();
   Class<T> targetClass = (Class<T>)
         ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);

   for (HttpMessageConverter<?> converter : this.messageConverters) {
      if (converter instanceof GenericHttpMessageConverter) {
         GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
         if (genericConverter.canRead(targetType, contextClass, contentType)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Reading [" + targetType + "] as \"" +
                     contentType + "\" using [" + converter + "]");
            }
            return genericConverter.read(targetType, contextClass, inputMessage);
         }
      }
      if (converter.canRead(targetClass, contentType)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Reading [" + targetClass.getName() + "] as \"" +
                  contentType + "\" using [" + converter + "]");
         }
         return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
      }
   }

   throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}

其实重要的是俩个参数,第一个是 contentType ,这个参数是从request的header中取出来的,比如你的请求header是 json ,那么这个参数的类型就是 application/json ,

第二个重要的参数就是 HttpMessageConverter 这个接口,这个接口的核心作用是,通过 contentType 判断是否request的值是否可读可写,Spring默认提供了7种messageConverters分别实现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, 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, 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 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(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, MediaType contentType, HttpOutputMessage outputMessage)
         throws IOException, HttpMessageNotWritableException;

}

其实就是read和write的判断,分别实现这个接口的 七个类是:

1.ResourceHttpMessageConverter:负责读取资源文件和写出资源文件数据; 

2.FormHttpMessageConverter:       负责读取form提交的数据(能读取的数据格式为 application/x-www-form-urlencoded,不能读取multipart/form-data格式数据)

3.MappingJacksonHttpMessageConverter:  负责读取和写入json格式的数据;

4.SouceHttpMessageConverter:                   负责读取和写入 xml 中javax.xml.transform.Source定义的数据;

5.Jaxb2RootElementHttpMessageConverter:  负责读取和写入xml 标签格式的数据;

6.AtomFeedHttpMessageConverter:              负责读取和写入Atom格式的数据;

7.RssChannelHttpMessageConverter:           负责读取和写入RSS格式的数据;

如果请求的contentType是json的话,那么通过循环判断可读会定位到 MappingJacksonHttpMessageConverter,其实Spring默认解析json用的是 jackson.然后会调用jackson的ObjectMapper去解析json,然后写入到要绑定的对象上。

private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
   try {
      return this.objectMapper.readValue(inputMessage.getBody(), javaType);
   }
   catch (IOException ex) {
      throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
   }
}

整个过程中代码其实走了挺多的,不过核心原理个人理解差不多就是这个样子,有什么不同的意见,欢迎大家指出。


### @RequestBody 注解支持多个参数的方法 在 Spring Boot 中,`@RequestBody` 注解用于将 HTTP 请求正文中的数据绑定到控制器方法的参数上。通常情况下,单个 `@RequestBody` 只能用来映射整个请求体的内容至一个对象。然而,在某些场景下确实存在需求要处理来自同一个请求体内的多种不同类型的输入。 对于想要接收多份独立的数据结构或者说是想让 `@RequestBody` 同时作用于多个参数的情况,可以采取如下几种策略: #### 将多个实体封装在一个类里 最常见的方式就是创建一个新的包含所有需要字段的对象模型来承载这些信息。比如如果希望接受两个不同的 DTO (Data Transfer Object),那么可以把它们组合起来形成一个新的传输对象[^1]。 ```java public class CombinedRequest { private User user; private Address address; // Getters and Setters... } ``` 接着定义相应的接口逻辑: ```java @PostMapping("/createUserAndAddress") public ResponseEntity<String> createUserAndAddress(@Valid @RequestBody CombinedRequest combinedRequest){ userService.save(combinedRequest.getUser()); addressService.save(combinedRequest.getAddress()); return new ResponseEntity<>("Successfully created", HttpStatus.CREATED); } ``` 这种方法简单明了,并且易于理解和维护。 #### 利用 Map 结构传参 另一种替代方案是利用 Java 的内置类型如 `Map<String,Object>` 来代替自定义 POJOs 。这种方式更加灵活但也可能牺牲了一定程度上的类型安全性和可读性[^5]。 ```java @PostMapping(value="/flexibleInput") public String flexibleInput(@RequestBody Map<String,Object> body){ // Process the map entries here. return "Processed"; } ``` 需要注意的是,虽然上述两种做法都可以解决向服务端发送复杂或复合型负载的需求,但在实际开发过程中还是推荐尽可能保持 API 设计清晰简洁,避免过度复杂的入参设计以免增加不必要的理解成本和技术债务。 #### 注意事项 无论采用哪种形式,都应当确认应用程序配置中有适当的消息转换组件(例如 Jackson),以便能够正确解析并序列化 JSON 数据。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值