我亲爱的各位大佬们好
♨️本篇文章记录的为SpringBoot Jackson序列化,ObjectMapper,configureMessageConverters, MappingJackson2HttpMessageConverter消息转换器相关内容,适合在学Java的小白,帮助新手快速上手,也适合复习中,面试中的大佬。
♨️如果文章有什么需要改进的地方还请大佬不吝赐教❤️
个人主页 : 阿千弟
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
(1)我们创建的Java对象被存储在Java堆中,当程序运行结束后,这些对象会被JVM回收。但在现实的应用中,可能会要求在程序运行结束之后还能读取这些对象,并在以后检索数据,这时就需要用到序列化。
(2)当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。
搭建springMvc框架时,遇到一个Controller接收实体参数问题:method为post,content-type为application/json; charset=utf-8的http请求,Controller接收是@RequestBody ,但无法序列化为Object类型,其它类型却可以,并且报415错误Unsupported Media Type,可以排除注解未开启、请求内容类型不一致问题,那么最有可能是jackson包没引用或者与spring版本号不一致。
Spring5.x引用jackson 2.9版本,Spring4.x引用jackson 2.6,Spring3.x引用jackson 1.9版本都是可以的。
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<!-- 注意版本应该与 Springboot版本匹配。看SpringBoot版本发布日期去Maven找对应的版本号 -->
</dependency>
这里我用到springboot版本是2.5.4, 所对应的版本jackson版本是2.12.4
Jackson ObjectMapper类(com.fasterxml.jackson.databind.ObjectMapper)是使用Jackson解析JSON最简单的方法。Jackson ObjectMapper可以从字符串、流或文件解析JSON,并创建Java对象或对象图来表示已解析的JSON。将JSON解析为Java对象也称为从JSON反序列化Java对象
Jackson ObjectMapper也可以从Java对象创建JSON. 从Java对象生成JSON的过程也被称为序列化Java对象到JSON
Jackson对象映射器(Object Mapper)可以把JSON解析为用户自定义类对象, 或者解析为JSON内置的树模型的对象
@JsonProperty 注解指定一个属性用于 JSON 映射,默认情况下映射的 JSON 属性与注解的属性名称相同,不过可以使用该注解的 value 值修改 JSON 属性名,该注解还有一个 index 属性指定生成 JSON 属性的顺序,如果有必要的话。
@JsonIgnore 注解用于排除某个属性,这样该属性就不会被 Jackson 序列化和反序列化。
@JsonIgnoreProperties 注解是类注解。在序列化为 JSON 的时候,@JsonIgnoreProperties({“prop1”, “prop2”}) 会忽略 pro1 和 pro2 两个属性。在从 JSON 反序列化为 Java 类的时候,@JsonIgnoreProperties(ignoreUnknown=true) 会忽略所有没有 Getter 和 Setter 的属性。该注解在 Java 类和 JSON 不完全匹配的时候很有用。
@JsonIgnoreType 也是类注解,会排除所有指定类型的属性。
@JsonPropertyOrder 和 @JsonProperty的 index 属性类似,指定属性序列化时的顺序。
@JsonRootName 注解用于指定 JSON 根属性的名称。
@JsonInclude(JsonInclude.Include.NON_NULL):对值为 null 的属性不进行序列化
@JsonInclude(JsonInclude.Include.NON_EMPTY) : 表示在序列化时,将值为null的字段排除掉。
添加到需要指定格式的日期属性上,指定日期属性序列化与反序列化时的格式。timezone = “GMT+8” 设置时区,表示 +8 小时,否则会少8小时。ObjectMapper 序列化 POJO 对象为 json 字符串时,Date 日期类型默认会转为 long 长整型,json 字符串反序列化为 POJO 时自动将长整型的日期转为 Date 类型。
Java 对象与 Json 字符串的转换 | |
---|---|
String writeValueAsString(Object value) | 1、用于将任何 Java 对象(如 POJO、List、Set、Map等)序列化为 json 字符串,如果对象中某个属性的值为 null,则默认也会序列化为 null;2、如果 value 为 null,返回序列化的结果也返回 null |
byte[] writeValueAsBytes(Object value) | 将 java 对象序列化为 字节数组 |
writeValue(File resultFile, Object value) | 将 java 对象序列化并输出指定文件中 |
writeValue(OutputStream out, Object value) | 将 java 对象序列化并输出到指定字节输出流中 |
writeValue(Writer w, Object value) | 将 java 对象序列化并输出到指定字符输出流中 |
T readValue(String content, Class valueType) | 1、从给定的 JSON 字符串反序列化为 Java 对象;2、content 为空或者为 null,都会报错3、valueType 表示反序列化的结果对象,可以是任何 java 对象,比如 POJO、List、Set、Map 等等. |
T readValue(byte[] src, Class valueType) | 将 json 内容的字节数组反序列化为 java 对象 |
T readValue(File src, Class valueType) | 将本地 json 内容的文件反序列化为 java 对象 |
T readValue(InputStream src, Class valueType) | 将 json 内容的字节输入流反序列化为 java 对象 |
T readValue(Reader src, Class valueType) | 将 json 内容的字符输入流反序列化为 java 对象 |
T readValue(URL src, Class valueType) | 通过网络 url 地址将 json 内容反序列化为 java 对象 |
ObjectMapper objectMapper = new ObjectMapper();
//去掉默认的时间戳格式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置为东八区
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 设置输入:禁止把POJO中值为null的字段映射到json字符串中
objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
//空值不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//反序列化时,属性不存在的兼容处理
objectMapper.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//序列化时,日期的统一格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//序列化日期时以timestamps输出,默认true
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//序列化枚举是以toString()来输出,默认false,即默认以name()来输出
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);
//序列化枚举是以ordinal()来输出,默认false
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,false);
//类为空时,不要抛异常
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//反序列化时,遇到未知属性时是否引起结果失败
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//单引号处理
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//解析器支持解析结束符
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
在默认情况下,如果Json数据中有多余的字段,那么在反序列化时Jackson发现无法找到对应的对象字段,便会抛出UnrecognizedPropertyException: Unrecognized field xxx异常,此时可以做如下配置:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
旧的配置方法(在2.9版本后已经过时)
新的配置方法
this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
原因 : 前端js对Long类型支持的精度不够,导致后端使用的Long传到前端丢失精度,比如现在分布式id生成算法“雪花算法”在使用中就会出现问题
解决方案:
在Long类型字段上使用注解标明序列化方式,代码量不大的情况可以考虑@JsonSerialize(using = ToStringSerializer.class)private Long id;
配置以下代码
SimpleModule simpleModule= newSimpleModule();
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
//暂时放弃对小long的转换,约定与前端交互数据时,大Long全部转换成字符串
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
当出现特定的需求时,比如:日期的序列化格式。此时需要自定义自己的消息转换器
假如有如下场景,前端传递过来一个日期字符串,但是后端需要使用Date类型进行接收,这时就需要一个类型转化器进行转化。
对于时间类型的处理,springmvc给我们提供了一个比较完善的解决方案,使用注解@DateTimeFormat,同时配合jackson提供的@JsonFormat注解几乎可以满足我们的所有需求。
@DateTimeFormat:当从requestParam中获取string参数并需要转化为Date类型时,会根据此注解的参数pattern的格式进行转化。
@JsonFormat:当从请求体中获取json字符序列,需要反序列化为对象时,时间类型会按照这个注解的属性内容进行处理。
这两个注解需要加在实体类的对应字段上即可:
// 对象和json互相转化的过程当中按照此转化方式转哈
@JsonFormat(
pattern = "yyyy年MM月dd日",
timezone = "GMT-8"
)
private Date birthday;
// 从requestParam中获取参数并且转化
@DateTimeFormat(pattern = "yyyy年MM月dd日")
private Date birthday;
处理的过程大致如下:
@Configuration
public class CustomObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public CustomObjectMapper() {
super();
//去掉默认的时间戳格式
this.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置为东八区
this.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 设置输入:禁止把POJO中值为null的字段映射到json字符串中
//this.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
// 空值不序列化
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 序列化枚举是以toString()来输出,默认false,即默认以name()来输出
this.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
我的项目中mvc配置是直接实现的WebMvcConfigurer,这两里面的方法一样,不同的是WebMvcConfigurationSupport里面有很多springboot为我们做的配置,而WebMvcConfigurer里面是空的;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
// 这个方法是用来配置静态资源的,比如html,js,css,等等
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
}
// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
//拓展mvc框架的消息转换器
@Autowired
private CustomObjectMapper customObjectMapper;
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置消息转换器,底层使用jackson将Java对象转成json
messageConverter.setObjectMapper(customObjectMapper);
//将我们自定义的消息转换器追加到mvc框架的集合转换器中
converters.add(0, messageConverter);
}
}
1.注解Bean形式:
// 这样做springboot会把我们自定义的converter放在顺序上的最高优先级(List的头部)
// 即有多个converter都满足Accpet/ContentType/MediaType的规则时,优先使用我们这个
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
return new MappingJackson2HttpMessageConverter();
}
2.springboot通过继承WebMvcConfigurerAdapter,重写configureMessageConverters。
(Spring3.x 用MappingJacksonHttpMessageConverter
Spring4.x 用MappingJackson2HttpMessageConverter)
// 通常在只有一个自定义WebMvcConfigurerAdapter时,会把这个方法里面添加的converter(s)依次放在最高优先级(List的头部)
// 虽然第一种方式的代码先执行,但是bean的添加比这种方式晚,所以方式二的优先级 大于 方式一
@Configuration
@EnableWebMvc
public class WebMvcConfigure extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters){
MappingJackson2HttpMessageConverter mjhmc=new MappingJackson2HttpMessageConverter(objectMapper());
converters.add(mjhmc);
}
@Bean
public ObjectMapper objectMapper(){
ObjectMapper om= Jackson2ObjectMapperBuilder.json().build();
om.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE,ToStringSerializer.instance);
simpleModule.addSerializer(SwordsAbstractModel.class,new CustomerJpaModelSerializer());
om.registerModule(simpleModule);
return om;
}
}
3.extendMessageConverters方式:
// 添加converter的第三种方式
// 同一个WebMvcConfigurerAdapter中的configureMessageConverters方法先于extendMessageConverters方法执行
// 可以理解为是三种方式中最后执行的一种,不过这里可以通过add指定顺序来调整优先级,也可以使用remove/clear来删除converter,功能强大
// 使用converters.add(xxx)会放在最低优先级(List的尾部)
// 使用converters.add(0,xxx)会放在最高优先级(List的头部)
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter());
}
在spring中配置WebMvc时有两种方法,一种是继承WebMvcConfigurationSupport,重写里面相应的方法,还有一种是继承WebMvcConfigurer的子抽象类WebMvcConfigurerAdapter,也是重写里面相应的方法,但是需要在配置类上添加@EnableWebMvc注解。那这两个类直接是什么关系呢?
配置HttpMessageConverters以用于读取或写入请求或响应的主体。如果未添加转换器,则会注册转换器的默认列表。请注意,向列表中添加转换器将关闭默认转换器注册。只需添加一个转换器而不影响默认注册,请考虑使用extendMessageConverters
用于在配置转换器后扩展或修改转换器列表的钩子。这可能很有用,例如允许注册默认转换器,然后通过此方法插入自定义转换器。
添加自定义消息转换器不覆盖默认转换器请使用extendMessageConverters
添加自定义会覆盖默认转换器使用configureMessageConverters
如果这篇【文章】有帮助到你,希望可以给我点个赞,创作不易,如果有对Java后端或者对
spring
感兴趣的朋友,请多多关注
个人主页 : 阿千弟