SpringMVC : 基于 Jackson 的数据转换处理

1,214 阅读6分钟

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

这一篇只关注一个小点 , 学习一下 SpringMVC 是如何进行数据转换.

二. 数据承接

2.1 数据转换常见用法

@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date createDate;

@JsonIgnore
private String ignoreField;

@JSONField(name = "age")
private String testAge;

以 fasterxml 为例 , 它属于外包 , 但是 SpringMVC 对其进行了集成 , 那么该功能是如何进行处理的呢 ?

2.2 数据转换的源码梳理

JSON 的转换流程主要为 HttpMessageConverter 模块 , 先来看一下之前的流程图

image.png

可以看到 , 会先通过 HandlerMethodArgumentResolverComposite 对属性进行解析 , 通过 HandlerMethodReturnValueHandlerComposite 对返回进行解析 , 他们都会通过 AbstractMessageConverterMethodArgumentResolver 进行统一的处理.

2.2.1 MessageConverter 的加载和初始化

// 注意 , 该类在 Spring AutoConfigure 中 , 并不是 MVC 专属
private static class MessageConverterInitializer implements Runnable {

   @Override
   public void run() {
      // 此处创建了一个对应的 FormHttpMessageConverter 扩展 ,增加对XML和基于json的部件的支持
      new AllEncompassingFormHttpMessageConverter();
   }

}

// 其中预加载了多种解析的类
public AllEncompassingFormHttpMessageConverter() {

   addPartConverter(new SourceHttpMessageConverter<>());
   
   // JAXB是Java Architecture for XML Binding的缩写,可以将一个Java对象转变成为XML格式,反之亦然
   if (jaxb2Present && !jackson2XmlPresent) {
      addPartConverter(new Jaxb2RootElementHttpMessageConverter());
   }
   // 处理JSON和XML格式化的类库 
   if (jackson2Present) {
      addPartConverter(new MappingJackson2HttpMessageConverter());
   }
   
   // Gson是谷歌官方推出的支持 `JSON -- Java Object` 相互转换的 Java`序列化/反序列化` 库
   else if (gsonPresent) {
      addPartConverter(new GsonHttpMessageConverter());
   }
   // 与 JSON 不同 , jsonb是保存为二进制格式的
   //- jsonb通常比json占用更多的磁盘空间(有些情况不是)
   //- jsonb比json的写入更耗时间
   //- json的操作比jsonb的操作明显更耗时间(在操作一个json类型值时需要每次都去解析)
   else if (jsonbPresent) {
      addPartConverter(new JsonbHttpMessageConverter());
   }
   // jackson 并不是值处理 JSON 的 , 这个转换器用于读写XML编码数据的扩展组件
   if (jackson2XmlPresent) {
      addPartConverter(new MappingJackson2XmlHttpMessageConverter());
   }
   // 可以读写Smile数据格式 
   if (jackson2SmilePresent) {
      addPartConverter(new MappingJackson2SmileHttpMessageConverter());
   }
}

- jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
- jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
               ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
- jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
- jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
- gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
- jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);

2.2.2 转化的入口

MessageConvert 转换的核心入口为 AbstractMessageConverterMethodArgumentResolver , 来看一下处理逻辑 (之前已经看过相关的代码 , 这里只截取一部分 -> juejin.cn/post/696584…)

// C- AbstractMessageConverterMethodArgumentResolver # readWithMessageConverters

// 补充一 : messageConverters 列表
// - org.springframework.http.converter.ByteArrayHttpMessageConverter
// - org.springframework.http.converter.StringHttpMessageConverter
// - org.springframework.http.converter.ResourceHttpMessageConverter
// - org.springframework.http.converter.ResourceRegionHttpMessageConverter
// - org.springframework.http.converter.xml.SourceHttpMessageConverter
// - org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
// - org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
// - org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

for (HttpMessageConverter<?> converter : this.messageConverters) {

   // 获取 Converter 类
   Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
   
   // 如果是 GenericHttpMessageConverter 需要进行转换
   // GenericHttpMessageConverter 接口继承了 HttpMessageConverter 接口
   GenericHttpMessageConverter<?> genericConverter =
         (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
         
   // 判断该 converter 是否可以处理该数据
   if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
         (targetClass != null && converter.canRead(targetClass, contentType))) {
         
      // converter 主要对 RequestBody 和 ResponseBody 的数据进行处理
      if (message.hasBody()) {
      
         // 前置处理操作 , 主要是 Advice -> JsonViewRequestBodyAdvice
         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;
   }
}

Converter 有很多种 , 此处以 AbstractJackson2HttpMessageConverter 为例 , 其向下的处理流程为 :

  • Step 1 : AbstractJackson2HttpMessageConverter # read : 进入 Converter 解析操作
  • Step 2 : AbstractJackson2HttpMessageConverter # readJavaType : 获取 Java 类型
  • Step 3 : CollectionDeserializer # deserialize : 进入转码解析
  • Step 4 : BeanDeserializer # deserializeFromObject : 循环处理 Object Param
  • Step 5 : deserializeAndSet : 解析并且设置数据

Step 4 : 循环处理 Object Param

最主要的操作就是从 deserializeFromObject 开始 ,此处将value 解析为 Bean

public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
    
    // ObjectIdReader 对象知道如何反序列化对象id
    // 如果 TokenId 表示为属性名 , 则直接处理该参数  , 因为其不需要其他序列化处理
    if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) {
        if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
                && _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) {
            return deserializeFromObjectId(p, ctxt);
        }
    }
    
    // 如果JVM并未给该实体类添加默认无参构造函数 , 此处即为true , 进入如下逻辑
    if (_nonStandardCreation) {
        // 如果其中一个属性具有“unwrapped”值,则需要单独的helper对象
        // PS : 
        if (_unwrappedPropertyHandler != null) {
            return deserializeWithUnwrapped(p, ctxt);
        }
        
        // 属性使用外部类型id
        if (_externalTypeIdHandler != null) {
            return deserializeWithExternalTypeId(p, ctxt);
        }
        Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
        if (_injectables != null) {
            injectValues(ctxt, bean);
        }

        return bean;
    }
    
    // 使用默认构造器创建一个当前 Body 对应的 Bean
    final Object bean = _valueInstantiator.createUsingDefault(ctxt);
    // 将 Bean 设置到容器中 , 用于后续的序列化处理
    p.setCurrentValue(bean);
    if (p.canReadObjectId()) {
        Object id = p.getObjectId();
        if (id != null) {
            _handleTypedObjectId(p, ctxt, bean, id);
        }
    }
    if (_injectables != null) {
        injectValues(ctxt, bean);
    }
    
    // 指示反序列化的某些方面取决于所使用的活动视图
    if (_needViewProcesing) {
        // 获取激活的视图TODO : 这个地方好像很有趣
        Class<?> view = ctxt.getActiveView();
        if (view != null) {
            return deserializeWithView(p, ctxt, bean, view);
        }
    }
    if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
        
        // 获取属性名
        String propName = p.getCurrentName();
        
        // 核心 : 对属性进行循环操作
        do {
            // 迭代 token
            p.nextToken();
            
            // 获取当前属性对应的元数据参数
            SettableBeanProperty prop = _beanProperties.find(propName);
            if (prop != null) { // normal case
                try {
                    // 解析并且设置
                    prop.deserializeAndSet(p, ctxt, bean);
                } catch (Exception e) {
                    wrapAndThrow(e, bean, propName, ctxt);
                }
                continue;
            }
            
            // 处理无法解析的对象
            handleUnknownVanilla(p, ctxt, bean, propName);
        } while ((propName = p.nextFieldName()) != null);
    }
    return bean;
}

// PS : TokenID 是什么 ? 
// TokenID 是用于返回结果的基本标记类型的枚举 , 因为 JSON 中的数据是无格式的 ,
// 所以需要通过 Token 表示其格式 , JsonTokenId 中共包含了如下类型 : 
public final static int ID_NO_TOKEN = 0;
public final static int ID_START_OBJECT = 1;
public final static int ID_END_OBJECT = 2;
public final static int ID_START_ARRAY = 3;
public final static int ID_END_ARRAY = 4;
// 属性名
public final static int ID_FIELD_NAME = 5;
public final static int ID_STRING = 6; // 字符串
public final static int ID_NUMBER_INT = 7; // INT
public final static int ID_NUMBER_FLOAT = 8; // Float 
public final static int ID_TRUE = 9;
public final static int ID_FALSE = 10;
public final static int ID_NULL = 11;
public final static int ID_EMBEDDED_OBJECT = 12;

image.png

Step 5 : 数据转换和设置

public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,Object instance) throws IOException{

    Object value;
    
    // null 值得处理
    if (p.hasToken(JsonToken.VALUE_NULL)) {
        if (_skipNulls) {
            return;
        }
        value = _nullProvider.getNullValue(ctxt);
    } else if (_valueTypeDeserializer == null) {
        // 此处值已经转换完成 , 此处会有对应的序列化类 
        // - DateDeserializers
        value = _valueDeserializer.deserialize(p, ctxt);
        
        // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
        if (value == null) {
            if (_skipNulls) {
                return;
            }
            // 如果处理完还是 null ,则进入 null 值得相关处理
            value = _nullProvider.getNullValue(ctxt);
        }
    } else {
        value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
    }
    try {
    
        // 最终 : 反射设置到 setter 方法中
        // 这也是为什么 setter 不存在时值不会设置得原因
        _setter.invoke(instance, value);
    } catch (Exception e) {
        _throwAsIOE(p, e, value);
    }
}

Step 6 : 具体类进行序列化

此处以 DateDeserializers 为例 , 对应得序列化类还有好多 , 有兴趣得可以进去看看

// C- DateDeserializers
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)throws IOException
    {
        if (_customFormat != null) {
            if (p.hasToken(JsonToken.VALUE_STRING)) {
                // 2021-10-21 14:20:55
                String str = p.getText().trim();
                if (str.length() == 0) {
                    return (Date) getEmptyValue(ctxt);
                }
                // 还上锁进行处理了
                synchronized (_customFormat) {
                    try {
                        // 核心 : format 对时间进行处理
                        return _customFormat.parse(str);
                    } catch (ParseException e) {
                        return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
                                "expected format "%s"", _formatString);
                    }
                }
            }
        }
        // 如果无法处理, 则交给父类处理 , 很常见得使用方式 , 类似于双亲委派得思路, 棒啊
        return super._parseDate(p, ctxt);
    }
}

image.png

这里再来回顾下 , DateFormat 是什么时候注入的 ?

在属性进入得时候 , 会进行 createContextual 操作 ,为对象创建一个容器上下文进行处理 (容器的处理很有意思 , 有机会也要看看 , 理解这种思想)

  • CollectionDeserializer # createContextual : 创建容器
  • DeserializationContext # findContextualValueDeserializer : 查找当前 value 对应的序列化类
public static JsonDeserializer<?> find(Class<?> rawType, String clsName)
{
    if (_classNames.contains(clsName)) {
        // Start with the most common type
        if (rawType == Calendar.class) {
            return new CalendarDeserializer();
        }
        // 最终根据时间类型选择对应的时间序列化类
        // 默认构造器中就会设置 -> public DateDeserializer() { super(Date.class); }
        if (rawType == java.util.Date.class) {
            return DateDeserializer.instance;
        }
        if (rawType == java.sql.Date.class) {
            return new SqlDateDeserializer();
        }
        if (rawType == Timestamp.class) {
            return new TimestampDeserializer();
        }
        if (rawType == GregorianCalendar.class) {
            return new CalendarDeserializer(GregorianCalendar.class);
        }
    }
    return null;
}

容器就像一个小车库, 当准备买车后 , 为他准备各种工具 , 用于自己的保养和维护 , 来解决各种问题 ,为其进行改装 ,不过这种思想 , 在单调的系统中其实很难实现

三 .数据导出

那么数据导出时是如何进行转换处理的呢 ?

write 数据同样通过 for 循环 messageConverters 来进行处理 , 其调用流程如下 :

  • RequestMappingHandlerAdapter # handleInternal : 此时在其中进行 invokeAndHandle 方法进行处理
  • RequestMappingHandlerAdapter # invokeHandlerMethod :
  • ServletInvocableHandlerMethod # invokeAndHandle : 准备 Return Value
  • HandlerMethodReturnValueHandlerComposite # handleReturnValue :
  • RequestResponseBodyMethodProcessor # handleReturnValue :
  • AbstractMessageConverterMethodProcessor # writeWithMessageConverters : 处理 converter 列表

writeWithMessageConverters 中核心流程如下 ,我们重点关注一下 :

for (HttpMessageConverter<?> converter : this.messageConverters) {
   GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
         (GenericHttpMessageConverter<?>) converter : null);
   if (genericConverter != null ?
         ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
         converter.canWrite(valueType, selectedMediaType)) {
         
      // Step 1 : 与 read 不同得一大点就是没有 afterWrite
      body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
            inputMessage, outputMessage);
            
      if (body != null) {
         Object theBody = body;
         addContentDispositionHeader(inputMessage, outputMessage);
         if (genericConverter != null) {
         
            // Step 2 :  
            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
         }
         else {
            ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
         }
      }
      else {

      }
      return;
   }
}

可以看到 , 使用的 converter 还是那些 , 处理的逻辑也大致相同 , 其主流程中分为2大部分 :

  • Step 1 : 前置处理 , 调用 RequestResponseBodyAdviceChain 链式处理
  • Step 2 : 调用具体的 converter 进行 write 操作

Advice 主要分为 RequestBodyAdvice 和 ResponseBodyAdvice 两种

mvc-bodyadvice.png

总结

有点偏题了 , 不算 MVC 的核心内容 , 主要是日常使用中出现了未生效的问题, 排查了一下原因 . 顺便出了一篇文档 , 以备以后使用.

Jackson 的底层看的很过瘾 , 很多地方想深入但是精力有限 , 它展现了序列化的核心流程 , 有机会一定要深入的看看

参考文档

developer.aliyun.com/article/769…