Spring@RequestBody自動マッピングモデルについて

18780 ワード

多くの場合、Springの注釈は私たちに多くの便利さを提供してくれましたが、その使い方しか知らず、その実行原理が分からず、時には間違いがあり、迅速な位置決めが難しい原因があります.今日は@Requestbodyという注釈に対する自分の考えを皆さんと共有したいと思います.
まずSpringが要求を処理するとき、要求の入り口はみんなが配置ファイルの中で配置したDispathcherServiceletという配布クラスである.実はこのクラスがrequestを受け入れることができる原理は、ServiceletのdoGet、doPostなどの方法を実現し、正式にControllerコードに達していないとき、論理を処理するときにControllerの反射例を取得することである.その注釈パラメータを反射インスタンスで取得することにより、注釈方法を実行した後にControllerに戻るので、@Requeat,@Validなどの注釈が構成されている場合、Controllerに戻るのはデータバインドと検証済みのオブジェクトであり、Controller構成@Requestbodyという注釈の場合、SpringはA b s t r a c t M e s s a g e C o n v e r MethodArgumentResolverという親クラスの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);
}

実は重要なのは2つのパラメータで、1つ目はcontentTypeで、このパラメータはrequestのheaderから取り出されました.例えば、あなたのリクエストheaderがjsonであれば、このパラメータのタイプはapplication/jsonです.
2つ目の重要なパラメータは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の判断で、それぞれこのインタフェースを実現する7つのクラスは:
1.ResourceHttpMessageConverter:リソースファイルの読み取りとリソースファイルデータの書き出しを担当します.
2.FormHttpMessageConverter:formが発行したデータの読み取りを担当する(読み込めるデータ形式はアプリケーション/x-www-form-urlencoded、multipart/form-data形式データの読み取りはできない)
3.MappingJacksonHttpMessageConverter:json形式のデータの読み取りと書き込みを担当する.
4.SouceHttpMessageConverter:xmlのjavaxの読み取りと書き込みを担当します.xml.transform.Source定義のデータ;
5.Jaxb 2 RootElementHttpMessageConverter: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);
   }
}

変換元:https://blog.csdn.net/geyuezhen/article/details/52853675