首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 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 模块 , 先来看一下之前的流程图
可以看到 , 会先通过 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;
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);
}
}
这里再来回顾下 , 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 的核心内容 , 主要是日常使用中出现了未生效的问题, 排查了一下原因 . 顺便出了一篇文档 , 以备以后使用.
Jackson 的底层看的很过瘾 , 很多地方想深入但是精力有限 , 它展现了序列化的核心流程 , 有机会一定要深入的看看