当前位置:首页 >热点 >详解Spring自定义消息格式转换器及底层源码分析 式转如果没有会抛出异常

详解Spring自定义消息格式转换器及底层源码分析 式转如果没有会抛出异常

2024-06-30 20:23:59 [百科] 来源:避面尹邢网

详解Spring自定义消息格式转换器及底层源码分析

作者:Springboot实战案例锦集 开发 前端 在配置消息转换器时,详解息格析指明了当前这个消息转换器能够接收的自定内容类型,也就是义消客户端请求时需要设定Content-Type为application/fm。

假设现在要实现这样的式转一个消息格式:

入参:

详解Spring自定义消息格式转换器及底层源码分析 式转如果没有会抛出异常

name:张三,age:20

详解Spring自定义消息格式转换器及底层源码分析 式转如果没有会抛出异常

图片图片

详解Spring自定义消息格式转换器及底层源码分析 式转如果没有会抛出异常

接口接收对象Users

自定义消息转换器

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

配置消息转换器

@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);  }  }

在配置消息转换器时,指明了当前这个消息转换器能够接收的层源内容类型,也就是码分客户端请求时需要设定Content-Type为application/fm。

参数对象

public class Users {     private String name ;  private Integer age ;    @Override  public String toString() {     return "【name = " + this.name + ",详解息格析 age = " + this.age + "】" ;  }  }

Controller接口

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

测试

请求:

图片图片

图片图片


响应

图片图片


图片图片


源码分析为何自定义消息转换器时要重写那几个方法:

由于我们的接口参数用@RequestBody 注解了,系统采用了

RequestResponseBodyMethodProcessor这个参数解析器进行参数的自定处理。

整个处理流程的义消入口是DispatcherServlet中的这行代码:

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

接着进入RequestMappingHandlerAdapter#handleInternal方法中的这行代码:

mav = invokeHandlerMethod(request, response, handlerMethod);

接着进入RequestMappingHandlerAdapter#invokeHandlerMethod方法的这行代码:

invocableMethod.invokeAndHandle(webRequest, mavContainer);

接着进入ServletInvocableHandlerMethod#invokeAndHandle方法中的这行代码:

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

接着进入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);}

接着进入getMethodArgumentValues方法

图片图片

1、这里就开始判断有没有参数解析器可以处理,式转如果没有会抛出异常。换器

这里还会吧找到处理的及底参数解析器缓存起来

图片图片

this.argumentResolverCache.put(parameter, result);

这行代码缓存了当前可以处理的解析器。

2、层源开始解析参数,直接从缓存中获取。因为上一步已经得到了解析器。

图片图片

得到了解析器后:

图片图片

进行入选中的方法,这个方法最终会进入父类
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;}

该方法中的this.messageConverters数据如下:

图片图片

这里可以看到我们自定义的CustomHttpMessageConverter。

继续调试到我们自定义的这个Converter

图片图片

从这里看出,会执行 else(:)中的代码

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

这个canRead是父类中的方法:

图片图片

support这里就进入到了我们自定义的Converter中。

图片图片

继续就会进入到read方法,真正读取处理消息内容的代码了

图片图片

这里的readInternal就是我们自定义的方法了

图片图片


关于write的相关方法和read差不多,也就是判断能否write,然后调用对应的writeInternal方法。


责任编辑:武晓燕 来源: Spring全家桶实战案例源码 配置write转换器

(责任编辑:休闲)

    推荐文章
    热点阅读