0
点赞
收藏
分享

微信扫一扫

Jackson的@JsonIgnore失效原因探究及解决方案


因项目需求,需要开发一个方法级别的注解,有点类似于@Cacheable注解(暂时就叫@ZSCacheable),缓存到redis时可以选择过滤实体类的字段,也就是在实体类某字段上加@ZSCacheable这个字段才能被序列化,未添加此注解的字段不序列化,所以就涉及 字段过滤 的问题。

在字段过滤中猪哥使用Ignore失效了,最后通过debug+源码查看,找出了原因,结论如下:

结论: JsonGetter、JsonProperty、JsonSerialize.class、JsonView.class、JsonFormat.class、JsonTypeInfo.class、JsonRawValue.class、JsonUnwrapped.class、JsonBackReference.class、JsonManagedReference.class 这些注解会使Ignore失效,这是jackson设计如此,分析如下!

一、字段过滤的方案

尝试过两种方法:

方案1:过滤器

在实体类上加上​​@JsonFilter("myFilter")​​注解,然后在注解切面中加上拦截器,核心代码如下:

ObjectMapper om = new ObjectMapper();
SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider().setFailOnUnknownId(true);
// 这里添加自定义拦截器
simpleFilterProvider.addFilter("myFilter", new SimpleBeanPropertyFilter() {
@Override
protected boolean include(PropertyWriter writer) {
return writer.getAnnotation(ZSCacheable.class) != null;
}
});

om.writer(simpleFilterProvider);
try {
String dataStr = om.writeValueAsString(data);
stringRedisTemplate.opsForValue().set(dataKey, dataStr, expire, TimeUnit.SECONDS);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}

但是这样会有个问题:其他接口调序列化此实体类时,也会查找​​myFilter​​拦截器,这样就会影响其他接口,我不希望影响到其他接口,所以这个方案行不通!

方案2:内省器

内省器不需要在实体类上加注解,不影响其他的接口和操作,非常适合我的需求,所以最后猪哥选择自定义内省器的方案。

private final ObjectMapper objectMapper = new ObjectMapper();

{
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// 自定义内省器
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
private static final long serialVersionUID = 746160236290648021L;

// 忽略不带@ZSCacheable注解的字段
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
if (super.hasIgnoreMarker(m)) {
return true;
}
if (m instanceof AnnotatedField) {
ZSCacheable zsCacheable = m.getAnnotation(ZSCacheable.class);
return zsCacheable == null;
}
return false;
}
});
}

上面代码中重载了​​hasIgnoreMarker​​​方法,在方法中增加判断字段中是否包含​​ZSCacheable​​注解,如果不包含则忽略。

看起来很美好,但是发现在序列化时带上了父类中的​​createTime​​​ 和 ​​updateTime​​​字段,查看父类发现这两个字段加上了​​@JsonFormat​​对时间格式化的注解,猜想可能是这个注解影响了序列化,是jackson的bug还是有意为之呢?具体原因还需要debug查看源码一探究竟。

二、探究为什么Ignore失效

带着猜想,猪哥debug一点一点看源码,最后发现这是jackson有意为之,jackson在自省器内部定义了两个class数组:​​ANNOTATIONS_TO_INFER_SER​​​ 和 ​​ANNOTATIONS_TO_INFER_DESER​​,在扫描实体类字段的时候会判断字段或者方法上是否有这些注解,如果有就取消Ignore,那具体是在哪里将取消Ignore的?猪哥带大家看看流程。

Jackson的@JsonIgnore失效原因探究及解决方案_json

1. 扫描所有字段

jackson在序列化实体类时,会扫描所有字段、方法,我们进入到添加字段的方法里面看看。

Jackson的@JsonIgnore失效原因探究及解决方案_字段_02


添加字段方法中,定义了一个​​PropertyName​​​后面是否取消Ignore是根据这个属性来的,我们重点看看查找Name的方法:​​findNameForSerialization​​。

Jackson的@JsonIgnore失效原因探究及解决方案_字段_03


在​​findNameForSerialization​​中的赋值顺序逻辑是:

  1. ​JsonGetter​​​注解,如果有则把​​JsonGetter​​​注解的value赋值给​​PropertyName​
  2. ​JsonProperty​​​注解,如果有则把​​JsonProperty​​​注解的value赋值给​​PropertyName​
  3. 如果上面上个注解都没有,再判断注解上有没有默认的几个注解,如果有则给一个则把空字符串赋值给​​PropertyName​
  4. Jackson的@JsonIgnore失效原因探究及解决方案_redis_04


  5. Jackson的@JsonIgnore失效原因探究及解决方案_json_05

  6. 当字段上有上面那几个注解后,​​findNameForSerialization​​方法会将空字符串赋值给​​PropertyName​​,然后后面会判断​​PropertyName​​是否为空字符串,如果为空字符串(且不为null)则把字段名赋值给​​PropertyName​​。
  7. Jackson的@JsonIgnore失效原因探究及解决方案_字段_06


至此,我们已经清楚了jackson扫描字段的逻辑:如果字段上有上面那几个注解,​​PropertyName​​​则为字段名,如果没有那几个注解​​PropertyName​​​则为null,后面判断是否要取消Ignore就是根据​​PropertyName​​来判断的。

2. 取消Ignore

扫描所有字段后,执行 删除不想要属性的方法,这里会判断他的​​PropertyName​​​和​​Ignora​​属性,我们进去看看

Jackson的@JsonIgnore失效原因探究及解决方案_json_07


在​​_removeUnwantedProperties​​​方法中,有一个判断是否明显引入的方法​​isExplicitlyIncluded​​​,如果明显引入那就删除Ignore,使Ignore失效,我们进入​​isExplicitlyIncluded​​看看

Jackson的@JsonIgnore失效原因探究及解决方案_redis_08


在​​isExplicitlyIncluded​​​方法的逻辑就是判断​​PropertyName​​不为空!

Jackson的@JsonIgnore失效原因探究及解决方案_json_09


Jackson的@JsonIgnore失效原因探究及解决方案_字段_10


Jackson的@JsonIgnore失效原因探究及解决方案_json_11


看到这里我们就明白了jackson 取消Ignore的流程了:取消Ignore的根据就是​​PropertyName​​​是否有值, 而在扫描所有字段(方法)时带那几个注解的​​PropertyName​​就有值。

三、解决方案

我们知道在扫描字段时,如果字段带上了那几个注解,​​findNameForSerialization​​​会返回一个空字符串,如果不带那几个注解则返回null,所以我们可以重写​​findNameForSerialization​​方法:当返回空字符串时我们直接返回null。

Jackson的@JsonIgnore失效原因探究及解决方案_java_12


重写​​findNameForSerialization​​​方法,先执行父类原来的逻辑,如果​​PropertyName​​​为null或者空,则返回null,这样后面的逻辑就不会给​​PropertyName​​赋值了,最后也就不会取消Ignore了。

Jackson的@JsonIgnore失效原因探究及解决方案_字段_13

四、对比效果

解决前:

Jackson的@JsonIgnore失效原因探究及解决方案_json_14


解决后:

Jackson的@JsonIgnore失效原因探究及解决方案_redis_15


举报

相关推荐

0 条评论