詳解Spring自定義消息格式轉(zhuǎn)換器及底層源碼分析
假設(shè)現(xiàn)在要實現(xiàn)這樣的一個消息格式:
入?yún)ⅲ?/p>
name:張三,age:20
圖片
接口接收對象Users
自定義消息轉(zhuǎn)換器
public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
  private static Logger logger = LoggerFactory.getLogger(CustomHttpMessageConverter.class) ;
  
  // 這里指明了只要接收參數(shù)是Users類型的都能進(jìn)行轉(zhuǎn)換
  @Override
  protected boolean supports(Class<?> clazz) {
    return Users.class == clazz ;
  }
  // 讀取內(nèi)容進(jìn)行內(nèi)容的轉(zhuǎn)換
  @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 ;
  }
  // 如果將返回值以什么形式輸出,這里就是調(diào)用了對象的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) ;
    }
  }
}配置消息轉(zhuǎn)換器
@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);
  }
  
}在配置消息轉(zhuǎn)換器時,指明了當(dāng)前這個消息轉(zhuǎn)換器能夠接收的內(nèi)容類型,也就是客戶端請求時需要設(shè)定Content-Type為application/fm。
參數(shù)對象
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("接受到內(nèi)容:" + user) ;
    return user ;
  }
}測試
請求:
圖片
圖片
響應(yīng)
圖片
圖片
源碼分析為何自定義消息轉(zhuǎn)換器時要重寫那幾個方法:
由于我們的接口參數(shù)用@RequestBody 注解了,系統(tǒng)采用了
RequestResponseBodyMethodProcessor這個參數(shù)解析器進(jìn)行參數(shù)的處理。
整個處理流程的入口是DispatcherServlet中的這行代碼:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());接著進(jìn)入RequestMappingHandlerAdapter#handleInternal方法中的這行代碼:
mav = invokeHandlerMethod(request, response, handlerMethod);接著進(jìn)入RequestMappingHandlerAdapter#invokeHandlerMethod方法的這行代碼:
invocableMethod.invokeAndHandle(webRequest, mavContainer);接著進(jìn)入ServletInvocableHandlerMethod#invokeAndHandle方法中的這行代碼:
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);接著進(jìn)入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);
}接著進(jìn)入getMethodArgumentValues方法
圖片
1、這里就開始判斷有沒有參數(shù)解析器可以處理,如果沒有會拋出異常。
這里還會吧找到處理的參數(shù)解析器緩存起來
圖片
this.argumentResolverCache.put(parameter, result);
這行代碼緩存了當(dāng)前可以處理的解析器。
2、開始解析參數(shù),直接從緩存中獲取。因為上一步已經(jīng)得到了解析器。
圖片
得到了解析器后:
圖片
進(jìn)行入選中的方法,這個方法最終會進(jìn)入父類
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數(shù)據(jù)如下:
圖片
這里可以看到我們自定義的CustomHttpMessageConverter。
繼續(xù)調(diào)試到我們自定義的這個Converter
圖片
從這里看出,會執(zhí)行 else(:)中的代碼
targetClass != null && converter.canRead(targetClass, contentType)
這個canRead是父類中的方法:
圖片
support這里就進(jìn)入到了我們自定義的Converter中。
圖片
繼續(xù)就會進(jìn)入到read方法,真正讀取處理消息內(nèi)容的代碼了
圖片
這里的readInternal就是我們自定義的方法了
圖片
關(guān)于write的相關(guān)方法和read差不多,也就是判斷能否write,然后調(diào)用對應(yīng)的writeInternal方法。















 
 
 





 
 
 
 