• 关于Spring @RequestBody 自动映射模型原理


    关于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);
       }
    }

    整个过程中代码其实走了挺多的,不过核心原理个人理解差不多就是这个样子,有什么不同的意见,欢迎大家指出。
  • 相关阅读:
    VMware安装最新版CentOS7图文教程
    git 本地给远程仓库创建分支 三步法
    git如何利用分支进行多人开发
    题解 洛谷P6478 [NOI Online #2 提高组] 游戏
    题解 CF1146D Frog Jumping
    题解 洛谷P6477 [NOI Online #2 提高组] 子序列问题
    题解 LOJ2472 「九省联考 2018」IIIDX
    题解 CF1340 A,B,C Codeforces Round #637 (Div. 1)
    题解 LOJ3284 「USACO 2020 US Open Platinum」Exercise
    windows上的路由表
  • 原文地址:https://www.cnblogs.com/kelelipeng/p/10170385.html
Copyright © 2020-2023  润新知