文章目录
- 前言
- SpringBoot支持哪些Json框架的自动配置?
- 为什么SpringBoot默认使用的是Jackson 框架?
- Jackson比较重要的自动配置
- JacksonObjectMapperConfiguration
- ObjectMapper在SpringBoot中有什么用?
- JacksonObjectMapperBuilderConfiguration
- Jackson2ObjectMapperBuilderCustomizerConfiguration
- Jackson自定义行为
- 方式一:通过配置文件去自定义Jackson的行为
- 方式二:通过实现Jackson2ObjectMapperBuilderCustomizer 来自定义Jackson的行为
- 参考
前言
SpringBoot支持哪些Json框架的自动配置?
我们之前了解过,SpringBoot的自动配置在autoconfigue的jar包中定义。可以从中发现,SpringBoot至少可以支持配置三种Json框架:
- jsckson :org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
- gson :org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration
- jsonb:org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration
为什么SpringBoot默认使用的是Jackson 框架?
这是因为在spring-boot-starter-web
依赖包中已经依赖了Jackson的依赖包jackson-databind,在Jackson中的自动配置类中,有一个条件:@ConditionalOnClass(ObjectMapper.class),简单来说,就是在当前项目的classpath中找到ObjectMapper.class,才会构建这个bean,刚好这个类是在jackson-databind的包中的,因此使得Jackson变成了Springboot的默认Json处理器。
Jackson比较重要的自动配置
首先研究一下Jackson的自动配置类:JacksonObjectMapperConfiguration
里面的所有内部配置类,都是有一个条件:@ConditionalOnClass(Jackson2ObjectMapperBuilder.class),这个Jackson2ObjectMapperBuilder是在spring-web.jar中的,所以可以简单认为,只要是web的环境下,都会自动加载这几个配置类。
JacksonObjectMapperConfiguration
功能:往Spring容器中注册一个ObjectMapper的实例,需要用到Jackson2ObjectMapperBuilder 实例
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
ObjectMapper在SpringBoot中有什么用?
在Spring Boot Web 项目中,当使用JSON格式接收数据和返回数据的时候,Spring Boot 默认使用一个ObjectMapper实例来序列化响应和反序列化请求。
与@ResponseBody的关系:
HttpMessageConverter是Spring3.0新添加的一个接口,负责将请求信息转换为一个对象(类型为T),将对象(类型为T)输出为响应信息
当加入了Jackson的jar包之后,DispatchServlet会自动装配一个MappingJackson2HttpMessageConverter
ResponseBody注解就是利用这个Converter来实现JSON化的。
MappingJackson2HttpMessageConverter的构造方法就要求传入一个ObjectMapper实例。
题外话:这个MappingJackson2HttpMessageConverter具体是怎么注册到Spring容器中的?
首先,可以通过autoconfigure这个包,查看到所有的HttpMessasgeConverter都是由HttpMessageConvertersAutoConfiguration这个配置类进行注册的。
通过@Import处找到JacksonHttpMessageConvertersConfiguration,可以看见SpringBoot在Jackson包引入的情况已经为我们初始化了一个mappingJackson2HttpMessageConverter 的bean。
@Configuration(proxyBeanMethods = false)
class JacksonHttpMessageConvertersConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "jackson", matchIfMissing = true)
static class MappingJackson2HttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,
ignoredType = {
"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
}
JacksonObjectMapperBuilderConfiguration
JacksonObjectMapperConfiguration->JacksonObjectMapperBuilderConfiguration类
功能:
在上面的JacksonObjectMapperConfiguration 可以知道,要构造ObjectMapper,就需要Jackson2ObjectMapperBuilder 的实例。
JacksonObjectMapperBuilderConfiguration 就是用来注册Jackson2ObjectMapperBuilder 的实例。
而且通过customize方法可以知道,通过实现Jackson2ObjectMapperBuilderCustomizer 就可以对Jackson2ObjectMapperBuilder 实例实现某些自定义化功能。这里暂时先不介绍
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(applicationContext);
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
}
Jackson2ObjectMapperBuilderCustomizerConfiguration
JacksonObjectMapperConfiguration->Jackson2ObjectMapperBuilderCustomizerConfiguration
作用:
通过配置文件配置JacksonProperties 的属性来实现定制化
在上面介绍JacksonObjectMapperBuilderConfiguration 的配置类时说到过,通过实现Jackson2ObjectMapperBuilderCustomizer 就可以对Jackson2ObjectMapperBuilder 实例实现某些自定义化功能。
下面注册的StandardJackson2ObjectMapperBuilderCustomizer 就是一个Jackson2ObjectMapperBuilderCustomizer 的实现。
static class Jackson2ObjectMapperBuilderCustomizerConfiguration {
//...
@Bean
StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(
ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
}
@Override
public void customize(Jackson2ObjectMapperBuilder builder) {
if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
}
if (this.jacksonProperties.getTimeZone() != null) {
builder.timeZone(this.jacksonProperties.getTimeZone());
}
configureFeatures(builder, FEATURE_DEFAULTS);
configureVisibility(builder, this.jacksonProperties.getVisibility());
configureFeatures(builder, this.jacksonProperties.getDeserialization());
configureFeatures(builder, this.jacksonProperties.getSerialization());
configureFeatures(builder, this.jacksonProperties.getMapper());
configureFeatures(builder, this.jacksonProperties.getParser());
configureFeatures(builder, this.jacksonProperties.getGenerator());
configureDateFormat(builder);
configurePropertyNamingStrategy(builder);
configureModules(builder);
configureLocale(builder);
}
//...
}
Jackson自定义行为
方式一:通过配置文件去自定义Jackson的行为
通过上面的自动配置分析,实际上配置文件的方式也是通过Jackson2ObjectMapperBuilderCustomizer 接口来实现自定义的。
applicaton.yml
spring:
jackson:
# 设置属性命名策略,对应jackson下PropertyNamingStrategy中的常量值,
# SNAKE_CASE-返回的json驼峰式转下划线,json body下划线传到后端自动转驼峰式
property-naming-strategy: SNAKE_CASE
# 全局设置@JsonFormat的格式pattern
date-format: yyyy-MM-dd HH:mm:ss
# 当地时区
locale: zh
# 设置全局时区
time-zone: GMT+8
# 常用,全局设置pojo的属性的序列化方式
#不为空的属性才会序列化,具体属性可看JsonInclude.Include
default-property-inclusion: NON_NULL
# 常规默认,枚举类SerializationFeature中的枚举属性为key,
# 值为boolean设置jackson序列化特性,具体key请看SerializationFeature源码
serialization:
WRITE_DATES_AS_TIMESTAMPS: true # 返回的java.util.date转换成timestamp
FAIL_ON_EMPTY_BEANS: true # 对象为空时是否报错,默认true
# 枚举类DeserializationFeature中的枚举属性为key,
# 值为boolean设置jackson反序列化特性,具体key请看DeserializationFeature源码
deserialization:
# 常用,json中含pojo不存在属性时是否失败报错,默认true
FAIL_ON_UNKNOWN_PROPERTIES: false
# 枚举类MapperFeature中的枚举属性为key,值为boolean设置jackson ObjectMapper特性
# ObjectMapper在jackson中负责json的读写、json与pojo的互转、
# json tree的互转,具体特性请看MapperFeature,常规默认即可
mapper:
# 使用getter取代setter探测属性,如类中含getName()但不包含name属性与setName(),
# 传输的vo json格式模板中依旧含name属性
USE_GETTERS_AS_SETTERS: true #默认false
# 枚举类JsonParser.Feature枚举类中的枚举属性为key,值为boolean设置jackson JsonParser特性
# JsonParser在jackson中负责json内容的读取,具体特性请看JsonParser.Feature,一般无需设置默认即可
parser:
ALLOW_SINGLE_QUOTES: true # 是否允许出现单引号,默认false
# 枚举类JsonGenerator.Feature枚举类中的枚举属性为key,值为boolean设置jackson JsonGenerator特性,一般无需设置默认即可
# JsonGenerator在jackson中负责编写json内容,具体特性请看JsonGenerator.Feature
下面详细介绍几个我觉得平时可以用得上的
- property-naming-strategy
a. 作用
ⅰ. 用于定义响应对象->JSON以及JSON->请求对象的属性名匹配方法,例如 小驼峰->下划线
b. 策略介绍:都是对应jackson下PropertyNamingStrategy中的常量值
ⅰ. SNAKE_CASE: 响应对象驼峰转换为下划线,请求对象下划线转首字母小写驼峰,下面的策略均是如此,如 ageList - > age_list
ⅱ. UPPER_CAMEL_CASE 驼峰转换为首字母大写的驼峰,如 ageList - > ageList
ⅲ. LOWER_CAMEL_CASE 驼峰转换为首字母小写的驼峰,如 ageList - > AgeList
ⅳ. LOWER_CASE 驼峰转全小写,如 ageList - > age-list
ⅴ. KEBAB_CASE 驼峰转横杠,如 ageList - > age-list
ⅵ. LOWER_DOT_CASE 驼峰转逗号,如:ageList-> age.list - date-format
a. 作用:全局设置@JsonFormat的格式,@JsonFormat是用于定义响应对象中Date->String或者LocalDateTime->String的Json字符串格式,目前一般不用这种@JsonFormat方式,因为需要在每个使用到Date类型的地方注解,特别麻烦,还不如定义全局的反序列化器然后注册到Jackson的ObjectMapper中。
b. 这里发现个坑,配置文件中定义的date-format,只能应用于Date类型,没办法使用于LocalDateTime,但是如果在@JsonFormat中直接指定pattern,LocalDateTime是可以转换的-_-|| - default-property-inclusion
a. 首先介绍一个注解:@JsonInclude,这个注解用于设置序列化的条件,策略枚举类为JsonInclude
ⅰ. ALWAYS 总是参与序列化,这个是默认指定的
ⅱ. NON_NULL 不是为null才参与序列化
ⅲ. NON_ABSENT 为缺省值或者为 NULL不会参与序列化,专用于Optional
ⅳ. NON_EMPTY 为空字符串或者为 NULL不会参与序列化
b. 作用:而这个属性则是全局指定的序列化策略,满足指定条件才会序列化某个字段,这个可以看情况使用,也可以使用注册自定义的序列化器到ObjectMapper实现类似的功能
c. 坑:这个并不是类似date-format,它不需要配合注解使用,如果使用了@JsonInclude ,就会直接按@JsonInclude 指定的方式对字段序列化,而不是使用全局的序列化方式 - serialization
a. 作用:设置序列化时的特性,可设置值:key为SerializationFeature枚举,value是Boolean,代表是否启用,一般应用在接口的响应对象里面
b. 枚举介绍(只介绍部分)
ⅰ. 输出特性
- WRAP_ROOT_VALUE:在响应对象的最外层再添加一个根部的对象JSON层级
a. 如:{“age”:13} 变成 {“JsonResult”:{“age”:13}},根部json层级的属性名默认是响应对象的类名 - INDENT_OUTPUT:美化响应内容的JSON,不用一整串的返回给前端(-_-||),不过swagger自带美化功能,不影响开发
ⅱ. 错误处理特性 - WRITE_SELF_REFERENCES_AS_NULL:如果出现自身引用自身的情况,则把成员对象变成null,例如Book的成员对象里有Book类型,默认为false,用的时候要把FAIL_ON_SELF_REFERENCES改为false,不然试不出来(不过用到的情况也很少…)
ⅲ. 指定特定类型序列化的特性 - WRITE_DATES_AS_TIMESTAMPS:默认开启,Date类型序列化为时间戳(13位),(LocalDate和LocalDateTime会变成年,月,日,时,分,秒的数组)
- WRITE_DATE_KEYS_AS_TIMESTAMPS:默认关闭,当使用Map<Date,Object>的时候,Date的key值序列化之后变成时间戳(13位)
- WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS:默认关闭,char[] 转换为一个JSON数组,其中每个元素都是一个String值
- WRITE_ENUMS_USING_TO_STRING:默认关闭,应用于枚举类,如果关闭状态,则返回Enum.name(),即名称,开启时返回toString().如果没有重写的话,其实都是一样的作用。
- WRITE_ENUMS_USING_INDEX:默认关闭,开启时返回枚举的ordinal() 值。
- WRITE_ENUM_KEYS_USING_INDEX:默认关闭,应用于以Enum为key的map。开启时会序列化为枚举的ordinal() 值作为key值
- WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED:默认关闭,开启时,响应对象的集合或数组中当前仅有一个元素时,将元素的属性往上提一次,也就是这个数组[]变成了{}
- deserialization
a. 作用:设置反序列化时的特性,可设置值:key为DeserializationFeature枚举,value是Boolean,代表是否启用,一般应用在接口的请求对象里面
b. 枚举介绍,我觉得用到的场景不是很多
- DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 在反序列化时,忽略未知字段
- 最近碰到过转化的问题了,举个例子:{“code”:4003,“successful”:true}, 如果接收的类型中没有successful的话,会出现异常,提示未知字段【“successful”】,这时候设置为false就会忽略这个未知字段
- mapper
a. 作用: 枚举类MapperFeature中的枚举属性为key,值为boolean设置jackson ObjectMapper特性,ObjectMapper在jackson中负责json的读写、json与pojo的互转,所以该属性是设置json与pojo的互转策略
b. 策略举例:
ⅰ. USE_GETTERS_AS_SETTERS: true :默认false,使用getter取代setter探测属性,如类中含getName()但不包含name属性与setName(),传输的vo json格式模板中依旧含name属性 - parser
a. 作用:枚举类JsonParser.Feature枚举类中的枚举属性为key,值为boolean设置jackson JsonParser特性,JsonParser在jackson中负责json内容的读取,具体特性请看JsonParser.Feature,一般无需设置默认即可
方式二:通过实现Jackson2ObjectMapperBuilderCustomizer 来自定义Jackson的行为
什么时候需要用到这种方式?
当jackson提供的配置不满足生产需求时,就需要使用这种方式去自定义。
Jackson2ObjectMapperBuilderCustomizer 里面也就是一个函数式接口,基于customize 方法对Jackson2ObjectMapperBuilder实例进行设置。
@FunctionalInterface
public interface Jackson2ObjectMapperBuilderCustomizer {
void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder);
}
那么,Jackson2ObjectMapperBuilder 又提供了什么可以让我们设置呢?
我在平时开发中用到以下的设置API:
- 自定义反序列化
a. public Jackson2ObjectMapperBuilder deserializers(JsonDeserializer<?>... deserializers) b. public Jackson2ObjectMapperBuilder deserializerByType(Class<?> type, JsonDeserializer<?> deserializer)
c. 上面两个注册反序列化器本质上没有区别,只是第一个API由于没有指定type,所以在JsonDeserializer中必须要重写handleType方法指定作用的类型 - 自定义序列化器
a. public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) b. public Jackson2ObjectMapperBuilder serializerByType(Class<?> type, JsonSerializer<?> serializer)
c. 同反序列化器的API,上面两个注册序列化器本质上没有区别,只是第一个API由于没有指定type,所以在JsonDeserializer中必须要重写handleType方法指定作用的类型
由于配置没有支持修改日期Date,LocaleDateTime,LocalDate的序列化和反序列化规则,所以只好使用以上API去自定义。
对于LocalDateTime和LocalDate的序列化,如果只是定义字符串格式,除了自定义自己的序列化器之外,还可以使用LocalDateTimeSerializer 和LocalDateSerializer 指定格式,这样的话就不需要新写一个类
除此之外,可以通过实现自定义的序列化器,在序列化时去除字符串前后空格,也就是实现一个trim的功能
然后还有其他的方法,例如:
- public Jackson2ObjectMapperBuilder indentOutput(boolean indentOutput)
- public Jackson2ObjectMapperBuilder dateFormat(DateFormat dateFormat)
都是配置的延伸,有兴趣可以了解一下
参考
https://developer.51cto.com/article/712411.html