有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot版本2.7.0
JSON(JavaScript Object Notation)
是一种轻量级的数据交换格式。易于人阅读和编写,可以在多种语言之间进行数据交换,同时也易于机器解析和生成。
JSON
是在2001 年开始推广使用的数据格式,在2005年-2006年正式成为主流的数据格式,雅虎和谷歌就在那时候开始广泛地使用JSON格式。
JSON
是一种取代XML
的数据结构,和XML
相比,它更小巧但描述能力却不差,由于它的小巧所以网络传输数据将减少更多流量从而加快速度。
JSON
其实也就是字符串, 只不过元素会使用特定的符号标注。
比如:
{"a": 1, "b": [1, 2, 3]}
在案例中:
{}
双括号表示对象
[]
中括号表示数组
""
双引号内是属性或值
:
冒号表示后者是前者的值(这个值可以是字符串、数字、也可以是另一个数组或对象)
目前JSON
已经是公认的、服务器与Web应用之间数据传输的API标准。
Gson
是由Google
公司开发的一个开源Java 类库。
特点:
Fastjson
是一个Java
语言编写的高性能的JSON
库,由阿里巴巴公司开发,号称其独创的算法解析速度超过所有json
库。
其核心开发人员是江湖中赫赫有名的温少
,大佬还开发了大名鼎鼎的Druid
数据库连接池。
目前使用最多的是1.x 版本,但是目前该版本好像只处于维护阶段了。
2022 年 4月份,Fastjson
发布了重构的2.X 版本
,重点优化之前版本存在的诸多问题,
Jackson
也是一个 Java 的用来处理 JSON
格式数据的类库,依托于Spring 生态,和其自身优越的性能,目前应该算是最受欢迎的JSON
框架。
Spring MVC
、Spring Boot
都是使用Jackson
作为默认的 JSON
库。
接下来我们熟悉下Jackson
中比较重要的几个组件:
1、ConfigFeature
ConfigFeature
从字面上看是配置特性的意思,在对象和JSON
之间进行序列化或反序列化时,肯定是需要配置策略的,比如时间转换的格式,而这个接口就是这个作用。
此接口一共有三个实现类(枚举)︰
SerializationFeature
就是序列化时,将对象转为JSON
字符串的特征配置,可配置项如下:
// jackson在序列化时,可以在json外面再包裹一层,官方叫做WRAP_ROOT_VALUE,通常叫做root对象
// 是否支持root对象,开启后@JsonRootName注解才会生效,一般没啥用
WRAP_ROOT_VALUE(false),
// 是否缩进输出,也就是格式化输出,和SQL 一样,换行输出
INDENT_OUTPUT(false),
// 没有Getter方法,属性为私有,也就是没有访问器时,是否抛出异常,设置为false时,输出 {}
FAIL_ON_EMPTY_BEANS(true),
// 自我引用时,是否报错
FAIL_ON_SELF_REFERENCES(true),
// 是否需要捕获异常然后重新抛出
WRAP_EXCEPTIONS(true),
// 当展开的对象有类型信息时会抛出错误。禁用时,对象将被解包并丢弃类型信息。
FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS(true),
// 如果出现自身引用自身的情况,则把成员对象变成null
WRITE_SELF_REFERENCES_AS_NULL(false),
// 当进行writevalue写时,是否自动帮你关闭实现了closeable的流
CLOSE_CLOSEABLE(false),
// WRITE写之后,是否自动FLUSH
FLUSH_AFTER_WRITE_VALUE(true),
// 日志类型是否按照TIMESTAMP格式输出
WRITE_DATES_AS_TIMESTAMPS(true),
// 时间类型的KEY是否按照TIMESTAMP格式输出
WRITE_DATE_KEYS_AS_TIMESTAMPS(false),
// 时区序列化为 +08:00 形式
WRITE_DATES_WITH_ZONE_ID(false),
// 是否使用分区日期时间值中的 timezoneoffset 的功能
WRITE_DATES_WITH_CONTEXT_TIME_ZONE(true),
// 确定表示时间段(durations, periods, ranges)的时间值是否默认使用数字或文本表示进行序列化的功能
WRITE_DURATIONS_AS_TIMESTAMPS(true),
// 是否把char[]数组也当做数组序列化
WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS(false),
// 序列化枚举是否使用tostring ()方法。默认是name()方法
WRITE_ENUMS_USING_TO_STRING(false),
// 序列化枚举是否使用数字序列
WRITE_ENUMS_USING_INDEX(false),
// 序列化枚举类型的KEY 是否使用数字序列
WRITE_ENUM_KEYS_USING_INDEX(false),
// 是否序列化MAP 类型中的NULL 值
// 2.9标为过时。使用(@JsonInclude注解或者ObjectNapper#configOverride(Class}来改变
@Deprecated
WRITE_NULL_MAP_VALUES(true),
// 2.8标为过时。使用@]sonInclude注解代替
@Deprecated
WRITE_EMPTY_JSON_ARRAYS(true),
// 序列化单元素数组时不以数组来输出
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED(false),
// 2.5标为过时。使用JsonGenerator.Feature#wRITE_BIGDECIMAL_AS_PLAIN代替
// 是否使用 {@link java.math.BigDecimal#toPlainString()} 序列化 {@link java.math.BigDecimal}
@Deprecated
WRITE_BIGDECIMAL_AS_PLAIN(false),
// 将日期时间戳写为纳秒
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS(true),
// 是否根据Map的key帮你排序
ORDER_MAP_ENTRIES_BY_KEYS(false),
// 是否应用Jackson的序列化缓存机制提升性能
EAGER_SERIALIZER_FETCH(true),
// 确定是否使用对象的真实 JVM-level 标识(false)比较对象标识的功能;或者,equals() 方法。
USE_EQUALITY_FOR_OBJECT_ID(false);
DeserializationFeature
就是反序列化时,将JSON
字符串转为对象的特征配置,可配置项如下:
// 是否将浮点数反序列化到BigDecimal
USE_BIG_DECIMAL_FOR_FLOATS(false),
// 对整型的处理是否使用BigInteger
USE_BIG_INTEGER_FOR_INTS(false),
// 是否使用Long去装int值
USE_LONG_FOR_INTS(false),
// 是否使用java的数组去装载JSON数组
USE_JAVA_ARRAY_FOR_JSON_ARRAY(false),
// 反序列化遇到不认识的属性时是否抛错
FAIL_ON_UNKNOWN_PROPERTIES(true),
// 对于intlong这种基本类型,若传null (没传)的话是否失败。
// true:没传就抛出JsonProcessingException 异常; false:没传就使用基本类型的默认值
FAIL_ON_NULL_FOR_PRIMITIVES(false),
// 反序列化到枚举类型是否允许传数字。true:数字的话就抛出异常
FAIL_ON_NUMBERS_FOR_ENUMS(false),
// 当传入的是非法的子类型的时候,是否失败。true:抛出异常;false:不管它
FAIL_ON_INVALID_SUBTYPE(true),
// 读取为树模型的时候,遇上相同的key是否抛异常。
FAIL_ON_READING_DUP_TREE_KEY(false),
// 是否处理已经被显示标记为忽略的属性
FAIL_ON_IGNORED_PROPERTIES(false),
// 遇到不能处理的Objectld时候如是否失败
FAIL_ON_UNRESOLVED_OBJECT_IDS(true),
// 当创建方法(不一定是构造函数)有多个参数,但是有参数的缺失时是否失败
FAIL_ON_MISSING_CREATOR_PROPERTIES(false),
// 给创建方法的参数传null时,是否失败
FAIL_ON_NULL_CREATOR_PROPERTIES(false),
// 当标注有(@JsonTypeInfo .As#EXTERNAL_PROPERTY注解的属性没传的时候是否失败
FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY(true),
// 是否去校验末尾的token。true:会自动多调用一次JsonParser#HnextToken—确保没有token了(如果还要就抛出异常); false:不会进行进—步的校验
FAIL_ON_TRAILING_TOKENS(false),
// 是否包装异常
WRAP_EXCEPTIONS(true),
// 单个值是否转为数组
ACCEPT_SINGLE_VALUE_AS_ARRAY(false),
// 确定是否可以将单值数组(在 JSON 中)值强制转换为相应值类型的功能
UNWRAP_SINGLE_VALUE_ARRAYS(false),
// 允许“解包”根级 JSON 值的功能
UNWRAP_ROOT_VALUE(false),
// 接受空字符串扎转为NULL
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT(false),
//
ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT(false),
// 浮点类型转为INT
ACCEPT_FLOAT_AS_INT(true),
// 枚举值的标准反序列化机制的功能:如果启用,则假定枚举已使用 Enum.toString() 的返回值进行序列化
READ_ENUMS_USING_TO_STRING(false),
// 允许将未知枚举值解析为空值的功能。如果禁用,未知的枚举值将引发异常
READ_UNKNOWN_ENUM_VALUES_AS_NULL(false),
// 允许忽略未知枚举值和通过 {@link com.fasterxml.jackson.annotation.JsonEnumDefaultValue @JsonEnumDefaultValue} 注释指定的预定义值的功能。
READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE(false),
// 将日期时间戳读取为纳秒
READ_DATE_TIMESTAMPS_AS_NANOSECONDS(true),
// 指定是否提供上下文的功能 {@link java.util.TimeZone} ({@link DeserializationContextgetTimeZone()} 应用于在反序列化时调整 DateTime 值,即使值本身包含时区信息
ADJUST_DATES_TO_CONTEXT_TIME_ZONE(true),
// 确定 {@link ObjectReader} 是否应该在可能的情况下急切地获取必要的 {@link JsonDeserializer} 的功能。
EAGER_DESERIALIZER_FETCH(true);
MapperFeature
则是可以控制JsonMapper
、0bjectMapper
、0bjectReader
、objectwriter
的映射/绑定行为,可配置项如下:
// 是否启用使用注解。true:将会使用AnnotationIntrospector去解析注解;false:忽略标注上的注解
USE_ANNOTATIONS(true),
// 是否可以通过get到集合/映射的引用来修改属性,而不需要专门提供set方法
USE_GETTERS_AS_SETTERS(true),
// 如何处理被transient修饰的关键字。true:会识别它(不再参与序列化反序列化) , false:忽略这个关键字PROPAGATE_瞬变标记(FALSE):如何处理被瞬态修饰的关键字。真:会识别它(不再参与序列化反序列化),假:忽略这个关键字
PROPAGATE_TRANSIENT_MARKER(false),
// 是否开启自动检测创建方法的特性。true:会找到public的构造方法 or 只有一个参数的valueOf方法;false:必须使用@JsonCreator注解去指定一个方法用于创建(空的构造方法除外,即使该特征关闭且是private都成)
AUTO_DETECT_CREATORS(true),
// 是否开启自动检测属性。true:所有的public field成员都会被认为是属性;false:那么只有被标为指定注解的才算(使用@JsonAutoDetect)
AUTO_DETECT_FIELDS(true),
// true:那么所有的0个参数且以get开头的方法都会被认为是属性;false:只能靠注解
AUTO_DETECT_GETTERS(true),
// 所有的0个参数且以is开头且返回值是boolean类型的会被认为是属性;false:只能依靠注解
AUTO_DETECT_IS_GETTERS(true),
// set方法(控制哪些属性可以被set进来)
AUTO_DETECT_SETTERS(true),
// 如果这个配置为false时,只有存在对应的构造器、setter或者field时,才调用getter。
REQUIRE_SETTERS_FOR_GETTERS(false),
// 标记为final的field是否也可以被get方法匹配
ALLOW_FINAL_FIELDS_AS_MUTATORS(true),
// private的field是否也可以被get方法匹配到
INFER_PROPERTY_MUTATORS(true),
// 是否支持JavaEE的注解@ConstructorProperties,效果同@JsonCreator
INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES(true),
// 是否允许调用AccessibleObject#setAccessible来让一切set方法or filed属性可以直接访问(注意:若你关闭此特征,也就是开启了权限检查的话,对性能是有较大损耗的,所以一般请不要这么做)
CAN_OVERRIDE_ACCESS_MODIFIERS(true),
// 针对紧邻的上特征的优化。也就是说若你上面特征开启了的话,理论上所有的权限都要去校验。但此处优化为若是public的话也依旧不执行访问检查了,提高public的整体效率
OVERRIDE_PUBLIC_ACCESS_MODIFIERS(true),
// 是否使用静态类型。true:使用运行时候的动态类型,false:使用声明时的静态类型
USE_STATIC_TYPING(false),
// 2.10新增的特征。是否使用BASE_TYPE当作反序列化时的多态类型的默认类型。true:使用;false:不使用。当然你也是可以手动通过@JsonTypeInfo.defaultImpl注解属性来指定的
USE_BASE_TYPE_AS_DEFAULT_IMPL(false),
// 这个特征和@JsonView息息相关。true:即使没有标注有@JsonView注解的属性,会被应用到所有的视图view里面;false:只有标注有@JsonView的属性才会进入到对应视图里
DEFAULT_VIEW_INCLUSION(true),
// 是否开启默认的序列化顺序。true:Bean的序列化会按照字母表顺序;false:无序。
SORT_PROPERTIES_ALPHABETICALLY(false),
// 首先对创建者属性进行排序
SORT_CREATOR_PROPERTIES_FIRST(true),
// 反序列化时是否对大小写敏感。true:不敏感;false:敏感
ACCEPT_CASE_INSENSITIVE_PROPERTIES(false),
// 反序列化时是否对大小写敏感。true:不敏感;false:敏感
ACCEPT_CASE_INSENSITIVE_ENUMS(false),
// 允许解析某些枚举的基于文本的值类型,但忽略大小写的特性。如对date/time的序列化,忽略上面的大小写
ACCEPT_CASE_INSENSITIVE_VALUES(false),
// 使用包装名称作为属性名称
USE_WRAPPER_NAME_AS_PROPERTY_NAME(false),
// 是否使用原始输出Bean Name。默认的是false的,是为了保证向下兼容
USE_STD_BEAN_NAMING(false),
// 是否允许再修改已经被命名的属性名
ALLOW_EXPLICIT_PROPERTY_RENAMING(false),
// 对scalar的一些类型的适配处理,保持默认开启即可
ALLOW_COERCION_OF_SCALARS(true),
// 是否开启重复模块的注册
IGNORE_DUPLICATE_MODULE_REGISTRATIONS(true),
// 对于不能被合并的属性(什么叫不能被合并的属性?)若你要合并时是否忽略。true:忽略它。false:抛出异常
IGNORE_MERGE_FOR_UNMERGEABLE(true),
// 阻止不安全的多态基类型
BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES(false),
// 使用默认值
APPLY_DEFAULT_VALUES(true);
2、ObjectMapper
ObjectMapper
是Jackson
最重要的一个类,是面向用户的高层API,我们需要进行JSON 转换,指定特征时,直接使用这个对象就可以了。
国内用的比较多的,应该就是Jackson
和Fastjson
了,我们应该怎么选呢?
首先简单看下他们的使用方式,比如将Java对象序列化为JSON字符串:
// Fastjson
String jsonString = JSON.toJSONString(user);
// Jackson
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = objectMapper.writeValueAsString(user);
将JSON字符串反序列化为JavaBean:
// Fastjson
Model model = JSON.parseObject(jsonStr, Model.class);
// Jackson
ObjectMapper objectMapper = new ObjectMapper();
Person person1 = objectMapper.readValue(jsonStr, Person.class);
Fastjson
直接使用JSON
类的静态方法进行转换操作,Jackson
需要实例化ObjectMapper
对象。
其他性能、安全方面的测试对比,可以自己在百度搜搜~
综合来说,二者都是非常优秀且成功的框架,性能上来说,Fastjson
可能更受一筹,但是从稳定性、安全性、代码规范性、扩展性来说,Jackson
稍占优势。
在网上有很多Jackson
和Fastjson
深度对比,到底改怎么选的文章,甚至还有阿里出品必属废品、公司已经禁用fastjson了种种言论。
Fastjson
作为一个几乎靠温少
一人之力扛起来的国产框架,其优秀和成功程度毋庸置疑,多多支持,国产才会进步。
Spring MVC
是一个WEB 框架,现在大多都是前后端分离开发,而前后端很多接口都是通过application/json
进行交互,在进行请求和响应处理时,毫无疑问,肯定要进行对象和JSON
转换,比如代码中响应结果都是对象,需要转换为JSON
传输给前端。
而Spring MVC
中进行对象转换操作的就是HttpMessageConverter
消息转换器,而对JSON
进行转换就是基于Jackson
实现的转换器,这也是默认选项,关于这些内容可以参考Spring MVC系列(8)-HttpMessageConverter之使用分析
Spring MVC
除了提供基于Jackson
实现的转换器之外,还提供了获取ObjectMapper
实例的两个类,一个是构建器,一个是FactoryBean 。
Spring Boot
提供了三个 JSON 库的集成:
Jackson
是默认首选。
可以看到在 spring-boot-starter-json
中,包含了Jackson
相关的依赖。
既然默认携带了Jackson
,就没有必要额外再费功夫引入另一种 JSON 库了,用默认的就OK了。
Spring Boot
对于Jackson
也提供了自动配置功能。
首先提供了配置属性类JacksonProperties
,方便我们直接在YML 配置文件中指定一些转换策略:
全部属性配置如下:
spring:
jackson:
constructor-detector: EXPLICIT_ONLY
# 设置日志格式化格式,配置为日期格式字符串或完全限定的日期格式类名。例如 yyyy-MM-dd HH:mm:ss
date-format: yyyy-MM-dd HH:mm:ss
# 宽松的全局默认设置
default-leniency: true
# 控制序列化期间包含的属性。使用 Jackson 的 JsonInclude.Include 枚举中的值之一进行配置。
default-property-inclusion: always
# 序列化配置 ,MAP 集合 , Map
serialization:
EAGER_SERIALIZER_FETCH: true
# 反序列化特征,Map
deserialization:
USE_BIG_DECIMAL_FOR_FLOATS: true
# ObjectMapper/JsonMapper特征,Map
mapper:
AUTO_DETECT_GETTERS: true
# 生成器JsonGenerator.Feature,Map
generator:
AUTO_CLOSE_TARGET: true
# 地区
locale: zh_CN
# 解析器 Map
# parser:
# 设置属性命名策略,对应jackson下PropertyNamingStrategy中的常量值,SNAKE_CASE-返回的json驼峰式转下划线,json body下划线传到后端自动转驼峰式
property-naming-strategy: SNAKE_CASE
# 全局时区
time-zone: GMT+8
# 可见性阈值,可用于限制自动检测哪些方法(和字段)。
visibility:
GETTER: ANY
Jackson2ObjectMapperBuilderCustomizer
从字面上理解时一个构造器的定制器,可以实现该接口,给Jackson2ObjectMapperBuilder
添加一些配置。
然后在Spring 容器注册一个该类型的Bean 对象:
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizeJackson2ObjectMapper() {
return builder -> builder
.indentOutput(true)
.propertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.simpleDateFormat("yyyyMMdd").build();
}
在实例化Jackson2ObjectMapperBuilder
(Spring MVC 提供的ObjectMapper 构建器)对象的时候,会调用容器中所有的定制器,配置构建者,这也就是提供了一个扩展点,可以使用这种方式定制我们的ObjectMapper
。
JacksonAutoConfiguration
为自动配置类,启动声明了一个定制器,将JacksonProperties
中配置的特征等传递给构建器。
注册了一个构建器,并调用所有的定制器的定制方法。
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
// 创建构建器
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
// 调用所有的定制器
this.customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder, List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Iterator var3 = customizers.iterator();
while(var3.hasNext()) {
Jackson2ObjectMapperBuilderCustomizer customizer = (Jackson2ObjectMapperBuilderCustomizer)var3.next();
customizer.customize(builder);
}
}
在自动配置类的static
块中,声明了两个默认特征,序列化时时间格式的数据不使用时间戳的方式,而原本的配置是true
,
static {
Map<Object, Boolean> featureDefaults = new HashMap();
featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
featureDefaults.put(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults);
}
最重要的是,可以看到Spring Boot
为我们注册了一个单例ObjectMapper
到容器中,是线程安全的,所以在使用Spring Boot
时不要再傻乎乎的去new 了~~~~
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}