Entorno: Springboot2.5.12
Supongamos que queremos implementar un formato de mensaje como este:
Ingresar:
nombre: Zhang San, edad: 20
La interfaz recibe el objeto Usuarios.
-
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) ;
}
}
}
-
Configurar el convertidor de mensajes
@Configuration
public 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.
-
objeto de parámetro
public class Users {
private String name ;
private Integer age ;
@Override
public String toString() {
return "【name = " + this.name + ", age = " + this.age + "】" ;
}
}
-
Interfaz del controlador
@RestController
@RequestMapping("/message")
public class MessageController {
@PostMapping("/save")
public Users save(@RequestBody Users user) {
System.out.println("接受到内容:" + user) ;
return user ;
}
}
-
prueba
preguntar:
respuesta
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
@Nullable
public 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
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é.
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.
Después de obtener el analizador:
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:
Aquí puede ver nuestro
CustomHttpMessageConverter personalizado.
Continuar depurando en nuestro convertidor personalizado
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:
El soporte aquí ingresa a nuestro Convertidor personalizado.
Si continúa, ingresará el método de lectura y leerá el código que procesa el contenido del mensaje.
El readInternal aquí es nuestro método personalizado.
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.