• 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);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    但是这样会有个问题:其他接口调序列化此实体类时,也会查找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;
                }
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

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

    二、探究为什么Ignore失效

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

    1. 扫描所有字段

    jackson在序列化实体类时,会扫描所有字段、方法,我们进入到添加字段的方法里面看看。
    在这里插入图片描述
    添加字段方法中,定义了一个PropertyName后面是否取消Ignore是根据这个属性来的,我们重点看看查找Name的方法:findNameForSerialization
    在这里插入图片描述
    findNameForSerialization中的赋值顺序逻辑是:

    1. JsonGetter注解,如果有则把JsonGetter注解的value赋值给PropertyName
    2. JsonProperty注解,如果有则把JsonProperty注解的value赋值给PropertyName
    3. 如果上面上个注解都没有,再判断注解上有没有默认的几个注解,如果有则给一个则把空字符串赋值给PropertyName
      在这里插入图片描述
      在这里插入图片描述
      当字段上有上面那几个注解后,findNameForSerialization方法会将空字符串赋值给PropertyName,然后后面会判断PropertyName是否为空字符串,如果为空字符串(且不为null)则把字段名赋值给PropertyName
      在这里插入图片描述

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

    2. 取消Ignore

    扫描所有字段后,执行 删除不想要属性的方法,这里会判断他的PropertyNameIgnora属性,我们进去看看
    在这里插入图片描述
    _removeUnwantedProperties方法中,有一个判断是否明显引入的方法isExplicitlyIncluded,如果明显引入那就删除Ignore,使Ignore失效,我们进入isExplicitlyIncluded看看
    在这里插入图片描述
    isExplicitlyIncluded方法的逻辑就是判断PropertyName不为空!
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    看到这里我们就明白了jackson 取消Ignore的流程了:取消Ignore的根据就是PropertyName是否有值, 而在扫描所有字段(方法)时带那几个注解的PropertyName就有值。

    三、解决方案

    我们知道在扫描字段时,如果字段带上了那几个注解,findNameForSerialization会返回一个空字符串,如果不带那几个注解则返回null,所以我们可以重写findNameForSerialization方法:当返回空字符串时我们直接返回null。
    在这里插入图片描述
    重写findNameForSerialization方法,先执行父类原来的逻辑,如果PropertyName为null或者空,则返回null,这样后面的逻辑就不会给PropertyName赋值了,最后也就不会取消Ignore了。
    在这里插入图片描述

    四、对比效果

    解决前:
    在这里插入图片描述
    解决后:
    请添加图片描述

  • 相关阅读:
    Re29:读论文 D2GCLF: Document-to-Graph Classifier for Legal Document Classification
    AUTOSAR从入门到精通番外篇(十)-嵌入式S19文件解析
    Java反射获取对象的属性值
    STM32 10个工程篇:1.IAP远程升级(六)
    用IntelliJ远程打断点调试
    华为OD机考算法题:评论转换输出
    C51串口通信(蓝牙)
    JDK20 + SpringBoot 3.1.0 + JdbcTemplate 使用
    贝壳网webpack案例
    Centos8部署LNMP架构
  • 原文地址:https://blog.csdn.net/u014044812/article/details/127682357