Detail the use of Http information converters for custom SpringMVC

  • 2020-12-05 17:11:15
  • OfStack

In SpringMVC, @RequestBody and @ResponseBody can be used to complete the conversion of request message to object and object to response message, respectively. The underlying flexible message conversion mechanism. Parse with HttpMessageConverter, which is the default configuration of the system, and bind the corresponding data to the object to be returned.

HttpInputMessage

In HttpMessageConverter's read() method, there is a formal parameter of HttpInputMessage, which is the internal abstraction of the recipient "request message" acted on by the message converter of SpringMVC. The message converter extracts the message from "request message" according to rules and converts it into the object declared in the method formal parameter.


package org.springframework.http;

import java.io.IOException;
import java.io.InputStream;

public interface HttpInputMessage extends HttpMessage {

  InputStream getBody() throws IOException;

}

HttpOutputMessage

In the write() method of HttpMessageConverter, there is a formal reference to HttpOutputMessage, which is the internal abstraction of the recipient "response message" acted upon by the message converter of SpringMVC, which writes the "response message" to the response message according to the rules specified in 1.


package org.springframework.http;

import java.io.IOException;
import java.io.OutputStream;

public interface HttpOutputMessage extends HttpMessage {

  OutputStream getBody() throws IOException;

}

HttpMessageConverter


/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.http.converter;

import java.io.IOException;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;


public interface HttpMessageConverter<T> {


  boolean canRead(Class<?> clazz, MediaType mediaType);

  boolean canWrite(Class<?> clazz, MediaType mediaType);

  List<MediaType> getSupportedMediaTypes();


  T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException;


  void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
      throws IOException, HttpMessageNotWritableException;

}

The HttpMessageConverter interface provides five methods:

canRead: Determines whether the converter can convert the requested content to an Java object canWrite: Determines whether the converter can convert an Java object into the returned content getSupportedMediaTypes: The TYPE of MediaType that is supported by this converter read: Reads the requested content and converts it to an Java object write: Converts the Java object to write the return

Among them, the parameters of read and write methods are HttpInputMessage and HttpOutputMessage objects, which respectively represent the request and response part in the first Http communication. The corresponding input stream and output stream can be obtained through THE getBody method.

Currently, a considerable number of converters have been provided by default in Spring, including:

名称 作用 读支持MediaType 写支持MediaType
ByteArrayHttpMessageConverter 数据与字节数组的相互转换 / application/octet-stream
StringHttpMessageConverter 数据与String类型的相互转换 text/* text/plain
FormHttpMessageConverter 表单与MultiValueMap<string, string=””>的相互转换 application/x-www-form-urlencoded application/x-www-form-urlencoded
SourceHttpMessageConverter 数据与javax.xml.transform.Source的相互转换 text/xml和application/xml text/xml和application/xml
MarshallingHttpMessageConverter 使用SpringMarshaller/Unmarshaller转换XML数据 text/xml和application/xml text/xml和application/xml
MappingJackson2HttpMessageConverter 使用Jackson的ObjectMapper转换Json数据 application/json application/json
MappingJackson2XmlHttpMessageConverter 使用Jackson的XmlMapper转换XML数据 application/xml application/xml
BufferedImageHttpMessageConverter 数据与java.awt.image.BufferedImage的相互转换 Java I/O API支持的所有类型 Java I/O API支持的所有类型

HttpMessageConverter matching process:

@ES65en annotation: Match the appropriate HttpMessageConverter one by one to read the data according to the ES68en-ES69en type of the header part of the Request object.


private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class paramType) throws Exception { 

  MediaType contentType = inputMessage.getHeaders().getContentType(); 
  if (contentType == null) { 
    StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType())); 
    String paramName = methodParam.getParameterName(); 
    if (paramName != null) { 
      builder.append(' '); 
      builder.append(paramName); 
    } 
    throw new HttpMediaTypeNotSupportedException("Cannot extract parameter (" + builder.toString() + "): no Content-Type found"); 
  } 

  List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); 
  if (this.messageConverters != null) { 
    for (HttpMessageConverter<?> messageConverter : this.messageConverters) { 
      allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); 
      if (messageConverter.canRead(paramType, contentType)) { 
        if (logger.isDebugEnabled()) { 
          logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType + "\" using [" + messageConverter + "]"); 
        } 
        return messageConverter.read(paramType, inputMessage); 
      } 
    } 
  } 
  throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); 
}

When annotating @ES75en: according to the Accept attribute of Request object header section (comma separated), traverse to the type in accept one by one to find the HttpMessageConverter that can be processed.


private void writeWithMessageConverters(Object returnValue, HttpInputMessage inputMessage, HttpOutputMessage outputMessage) 
        throws IOException, HttpMediaTypeNotAcceptableException { 
  List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept(); 
  if (acceptedMediaTypes.isEmpty()) { 
    acceptedMediaTypes = Collections.singletonList(MediaType.ALL); 
  } 
  MediaType.sortByQualityValue(acceptedMediaTypes); 
  Class<?> returnValueType = returnValue.getClass(); 
  List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); 
  if (getMessageConverters() != null) { 
    for (MediaType acceptedMediaType : acceptedMediaTypes) { 
      for (HttpMessageConverter messageConverter : getMessageConverters()) { 
        if (messageConverter.canWrite(returnValueType, acceptedMediaType)) { 
          messageConverter.write(returnValue, acceptedMediaType, outputMessage); 
          if (logger.isDebugEnabled()) { 
            MediaType contentType = outputMessage.getHeaders().getContentType(); 
            if (contentType == null) { 
              contentType = acceptedMediaType; 
            } 
            logger.debug("Written [" + returnValue + "] as \"" + contentType + 
                "\" using [" + messageConverter + "]"); 
          } 
          this.responseArgumentUsed = true; 
          return; 
        } 
      } 
    } 
    for (HttpMessageConverter messageConverter : messageConverters) { 
      allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); 
    } 
  } 
  throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes); 
}

Customize 1 JSON converter


class CustomJsonHttpMessageConverter implements HttpMessageConverter {

  //Jackson the Json The mapping class 
  private ObjectMapper mapper = new ObjectMapper();

  // The supported types of this converter: application/json
  private List supportedMediaTypes = Arrays.asList(MediaType.APPLICATION_JSON);

  /**
   *  Determines whether the converter can convert the input to Java type 
   * @param clazz    Transformational Java type 
   * @param mediaType  The request of MediaType
   * @return
   */
  @Override
  public boolean canRead(Class clazz, MediaType mediaType) {
    if (mediaType == null) {
      return true;
    }
    for (MediaType supportedMediaType : getSupportedMediaTypes()) {
      if (supportedMediaType.includes(mediaType)) {
        return true;
      }
    }
    return false;
  }

  /**
   *  Determine if the converter can be converted Java Type is converted to the specified output 
   * @param clazz    Transformational Java type 
   * @param mediaType  The request of MediaType
   * @return
   */
  @Override
  public boolean canWrite(Class clazz, MediaType mediaType) {
    if (mediaType == null || MediaType.ALL.equals(mediaType)) {
      return true;
    }
    for (MediaType supportedMediaType : getSupportedMediaTypes()) {
      if (supportedMediaType.includes(mediaType)) {
        return true;
      }
    }
    return false;
  }

  /**
   *  Supported by this converter MediaType
   * @return
   */
  @Override
  public List getSupportedMediaTypes() {
    return supportedMediaTypes;
  }

  /**
   *  Read the requested content and put the Json Converted to Java object 
   * @param clazz      Transformational Java type 
   * @param inputMessage  The request object 
   * @return
   * @throws IOException
   * @throws HttpMessageNotReadableException
   */
  @Override
  public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    return mapper.readValue(inputMessage.getBody(), clazz);
  }

  /**
   *  will Java Object conversion to Json Returns the content 
   * @param o        Objects that need to be transformed 
   * @param contentType   The return type 
   * @param outputMessage  Return receipt object 
   * @throws IOException
   * @throws HttpMessageNotWritableException
   */
  @Override
  public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    mapper.writeValue(outputMessage.getBody(), o);
  }
}

Custom MappingJackson2HttpMessage

It can be seen from the write method in AbstractHttpMessageConverter, the parent class of MappingJackson2HttpMessageConverter, that this method writes data to the output stream that returns the result through the writeInternal method, so it only needs to be overwritten:


@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
  return new MappingJackson2HttpMessageConverter() {
    // rewrite writeInternal Method, which encrypts the content before returning it 
    @Override
    protected void writeInternal(Object object,
                   HttpOutputMessage outputMessage) throws IOException,
        HttpMessageNotWritableException {
      // use Jackson the ObjectMapper will Java Object conversion to Json String
      ObjectMapper mapper = new ObjectMapper();
      String json = mapper.writeValueAsString(object);
      LOGGER.error(json);
      // encryption 
      String result = json + " Encryption! ";
      LOGGER.error(result);
      // The output 
      outputMessage.getBody().write(result.getBytes());
    }
  };
}
 

After that you also need to configure the custom converter into Spring, here by overriding the configureMessageConverters method in WebMvcConfigurer to add the custom converter:


// Add custom converters 
@Override
public void configureMessageConverters(List<httpmessageconverter<?>> converters) {
  converters.add(mappingJackson2HttpMessageConverter());
  super.configureMessageConverters(converters);
}

Related articles: