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

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

环境:Springboot2.5.12


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

入参:

name:张三,age:20


接口接收对象Users


  1. 自定义消息转换器
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. 配置消息转换器
@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);
  }
}

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

  1. 参数对象
public class Users {
  private String name ;
  private Integer age ;
  @Override
  public String toString() {
    return "【name = " + this.name + ", age = " + this.age + "】" ;
  }
}
  1. Controller接口
@RestController
@RequestMapping("/message")
public class MessageController {
  @PostMapping("/save")
  public Users save(@RequestBody Users user) {
    System.out.println("接受到内容:" + user) ;
    return user ;
  }
}
  1. 测试

请求:

响应


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

由于我们的接口参数用@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方法

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

接着进入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方法。

相关文章
|
2月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
75 1
|
27天前
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
27天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
26天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
1月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
106 14
|
2月前
|
安全 Java 应用服务中间件
如何将Spring Boot应用程序运行到自定义端口
如何将Spring Boot应用程序运行到自定义端口
87 0
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
44 1
|
3月前
|
缓存 JavaScript Java
Spring之FactoryBean的处理底层源码分析
本文介绍了Spring框架中FactoryBean的重要作用及其使用方法。通过一个简单的示例展示了如何通过FactoryBean返回一个User对象,并解释了在调用`getBean()`方法时,传入名称前添加`&`符号会改变返回对象类型的原因。进一步深入源码分析,详细说明了`getBean()`方法内部对FactoryBean的处理逻辑,解释了为何添加`&`符号会导致不同的行为。最后,通过具体代码片段展示了这一过程的关键步骤。
Spring之FactoryBean的处理底层源码分析
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
42 1
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
42 0