SpringMVC 基于 Jackson 的数据转换处理

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

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

二. 数据承接

2.1 数据转换常见用法

1
2
3
4
5
6
7
8
java复制代码@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 的加载和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
java复制代码// 注意 , 该类在 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…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
JAVA复制代码// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
java复制代码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 : 数据转换和设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码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 为例 , 对应得序列化类还有好多 , 有兴趣得可以进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码// 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 对应的序列化类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码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 中核心流程如下 ,我们重点关注一下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
java复制代码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…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%