Explicación detallada del convertidor de formato de mensajes personalizado de Spring y análisis del código fuente subyacente

Entorno: Springboot2.5.12


Supongamos que queremos implementar un formato de mensaje como este:

Ingresar:

nombre: Zhang San, edad: 20

imagen

La interfaz recibe el objeto Usuarios.


  1. Conversor de mensajes personalizados

public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
   
   
  private static Logger logger = LoggerFactory.getLogger(CustomHttpMessageConverter.class) ;    // 这里指明了只要接收参数是Users类型的都能进行转换  @Override  protected boolean supports(Class<?> clazz) {
   
       return Users.class == clazz ;  }  // 读取内容进行内容的转换  @Override  protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)      throws IOException, HttpMessageNotReadableException {
   
       String content = inToString(inputMessage.getBody()) ;    String[] keys = content.split(",") ;    Users instance = null ;    try {
   
         instance = (Users) clazz.newInstance();    } catch (Exception e1) {
   
         e1.printStackTrace() ;    }    for (String key : keys) {
   
         String[] vk = key.split(":") ;      try {
   
           Field[] fields = clazz.getDeclaredFields() ;        for (Field f:fields) {
   
             if (f.getName().equals(vk[0])) {
   
               f.setAccessible(true) ;            Class<?> type = f.getType() ;            if (String.class == type) {
   
                 f.set(instance, vk[1]) ;            } else if (Integer.class == type) {
   
                 f.set(instance, Integer.parseInt(vk[1])) ;            }            break ;          }        }      } catch (Exception e) {
   
           logger.error("错误:{}", e) ;      }    }    return instance ;  }
  // 如果将返回值以什么形式输出,这里就是调用了对象的toString方法。  @Override  protected void writeInternal(Object t, HttpOutputMessage outputMessage)      throws IOException, HttpMessageNotWritableException {
   
       outputMessage.getBody().write(t.toString().getBytes()) ;  }    @Override  protected boolean canWrite(MediaType mediaType) {
   
       if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
   
         return true;    }    for (MediaType supportedMediaType : getSupportedMediaTypes()) {
   
         if (supportedMediaType.isCompatibleWith(mediaType)) {
   
           return true;      }    }    return false;  }    private String inToString(InputStream is) {
   
       byte[] buf = new byte[10 * 1024] ;    int leng = -1 ;    StringBuilder sb = new StringBuilder() ;    try {
   
         while ((leng = is.read(buf)) != -1) {
   
           sb.append(new String(buf, 0, leng)) ;      }      return sb.toString() ;    } catch (IOException e) {
   
         throw new RuntimeException(e) ;    }  }
}
  1. Configurar el convertidor de mensajes

@Configurationpublic class WebConfig implements WebMvcConfigurer {
   
   
  @Override  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
   
       CustomHttpMessageConverter messageConvert = new CustomHttpMessageConverter() ;    List<MediaType> supportedMediaTypes = new ArrayList<>() ;    supportedMediaTypes.add(new MediaType("application", "fm")) ;    messageConvert.setSupportedMediaTypes(supportedMediaTypes) ;    converters.add(messageConvert) ;    WebMvcConfigurer.super.configureMessageConverters(converters);  }  }

Al configurar el convertidor de mensajes, se especifica el tipo de contenido que el convertidor de mensajes actual puede recibir, es decir, el cliente debe configurar el tipo de contenido en aplicación/fm al realizar la solicitud.

  1. objeto de parámetro

public class Users {
   
       private String name ;  private Integer age ;
    @Override  public String toString() {
   
       return "【name = " + this.name + ", age = " + this.age + "】" ;  }  }
  1. Interfaz del controlador

@RestController@RequestMapping("/message")public class MessageController {
   
       @PostMapping("/save")  public Users save(@RequestBody Users user) {
   
       System.out.println("接受到内容:" + user) ;    return user ;  }}
  1. prueba

preguntar:

imagen

imagen

respuesta

imagen

imagen


Análisis del código fuente: ¿por qué es necesario reescribir esos métodos al personalizar un convertidor de mensajes?

Dado que los parámetros de nuestra interfaz están anotados con @RequestBody  , el sistema utiliza

El analizador de parámetros RequestResponseBodyMethodProcessor procesa parámetros.

La entrada a todo el flujo de procesamiento es esta línea de código en DispatcherServlet :

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

Luego ingrese esta línea de código en el método
RequestMappingHandlerAdapter #handleInternal :

mav = invokeHandlerMethod(request, response, handlerMethod);

Luego ingrese esta línea de código en
el método RequestMappingHandlerAdapter#invokeHandlerMethod :

invocableMethod.invokeAndHandle(webRequest, mavContainer);

Luego ingrese esta línea de código en
el método ServletInvocableHandlerMethod#invokeAndHandle :

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

Luego ingrese el método invokeForRequest

@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,    Object... providedArgs) throws Exception {
   
   
  Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);  if (logger.isTraceEnabled()) {
   
       logger.trace("Arguments: " + Arrays.toString(args));  }  return doInvoke(args);}

Luego ingrese el método getMethodArgumentValues

imagen

1. Aquí comenzamos a determinar si existe un analizador de parámetros que pueda manejarlo, de lo contrario se generará una excepción.

Aquí también encontraremos el analizador de parámetros procesados ​​y lo almacenaremos en caché.

imagen

this.argumentResolverCache.put(parámetro, resultado);

Esta línea de código almacena en caché los analizadores que se pueden procesar actualmente.

2. Comience a analizar los parámetros y obtengalos directamente del caché. Porque el analizador se obtuvo en el paso anterior.

imagen

Después de obtener el analizador:

imagen

Realice el método seleccionado, que eventualmente ingresará el
siguiente método de la clase principal AbstractMessageConverterMethodArgumentResolver:

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,    Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
   
   
  MediaType contentType;  boolean noContentType = false;  try {
   
       contentType = inputMessage.getHeaders().getContentType();  }  catch (InvalidMediaTypeException ex) {
   
       throw new HttpMediaTypeNotSupportedException(ex.getMessage());  }  if (contentType == null) {
   
       noContentType = true;    contentType = MediaType.APPLICATION_OCTET_STREAM;  }
  Class<?> contextClass = parameter.getContainingClass();  Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);  if (targetClass == null) {
   
       ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);    targetClass = (Class<T>) resolvableType.resolve();  }
  HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);  Object body = NO_VALUE;
  EmptyBodyCheckingHttpInputMessage message;  try {
   
       message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
    for (HttpMessageConverter<?> converter : this.messageConverters) {
   
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();      GenericHttpMessageConverter<?> genericConverter =          (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);      if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :          (targetClass != null && converter.canRead(targetClass, contentType))) {
   
           if (message.hasBody()) {
   
             HttpInputMessage msgToUse =              getAdvice().beforeBodyRead(message, parameter, targetType, converterType);          body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :              ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));          body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);        }        else {
   
             body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);        }        break;      }    }  }  catch (IOException ex) {
   
       throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);  }
  if (body == NO_VALUE) {
   
       if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||        (noContentType && !message.hasBody())) {
   
         return null;    }    throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);  }
  MediaType selectedContentType = contentType;  Object theBody = body;  LogFormatUtils.traceDebug(logger, traceOn -> {
   
       String formatted = LogFormatUtils.formatValue(theBody, !traceOn);    return "Read \"" + selectedContentType + "\" to [" + formatted + "]";  });
  return body;}

Los datos de this.messageConverters en este método son los siguientes:

imagen

Aquí puede ver nuestro
CustomHttpMessageConverter personalizado.

Continuar depurando en nuestro convertidor personalizado

imagen

Se puede ver desde aquí que se ejecutará el código en else (:)

targetClass!= null && convertidor.canRead(targetClass, contentType)

Este canRead es un método en la clase principal:

imagen

El soporte aquí ingresa a nuestro Convertidor personalizado.

imagen

Si continúa, ingresará el método de lectura y leerá el código que procesa el contenido del mensaje.

imagen

El readInternal aquí es nuestro método personalizado.

imagen

Los métodos de escritura relacionados son similares a los de lectura, es decir, juzgan si se pueden escribir y luego llaman al método writeInternal correspondiente.

Supongo que te gusta

Origin blog.csdn.net/qq_45635347/article/details/132515875
Recomendado
Clasificación