原创

Jackson2 反序列化: setter 方法重载的坑

  • 这几天遇到一个自己埋的坑 :<( , 一个 bean 中有一个 LocalDateTime 字段, 用了注解或配置相应反序列化器, 都无法序列化, 就是提示错误:
com.fasterxml.jackson.databind.JsonMappingException: Problem deserializing property 'expireTime' (expected type: [simple type, class int]; actual type:
`java.time.LocalDateTime`), problem: argument type mismatch
 at [Source: (String)"{"code":"5ddj","expireTime":"2020-10-31 10:59:44 811","reuse":false,"image":null,"expired":false}"; line: 1, column: 29] (through reference chain:
 top.dcenter.ums.security.core.auth.validate.codes.image.ImageCode["expireTime"])

相关配置:

jackson

Bean

@Getter
@Setter
@ToString
public class ValidateCode implements Serializable {

    private static final long serialVersionUID = 8564646192066649173L;

    private String code;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss SSS", locale = "zh", timezone = "GMT+8")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    private LocalDateTime expireTime;
    /**
     * 是否复用, 如果复用, 不会重新产生验证码, 仍使用验证码失败的验证码
     */
    private Boolean reuse;

    public ValidateCode() {
        this.code = null;
        this.expireTime = null;
        this.reuse = false;
    }

    /**
     * 验证码构造器: 默认 <pre>reuse = false;</pre> 不复用
     * @param code      验证码
     * @param expireIn  秒
     */
    public ValidateCode(String code, int expireIn) {
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
        reuse = false;
    }

    public void setExpireTime(int expireIn) {
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }

    /**
     * 验证码构造器: 默认不复用
     * @param code      验证码
     * @param expireIn  过期日期
     * @param reuse     是否复用, 如果复用, 不会重新产生验证码, 仍使用验证码失败的验证码, 默认: false 即不复用
     */
    public ValidateCode(String code, int expireIn, Boolean reuse) {
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
        this.reuse = reuse;
    }

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}
  • 该配置的都配置了, 就是反序列化错误, 总不能有 LocalDateTimebean 都自定义一个反序列化器吧!

二 最后没办法只能 DEBUG ObjectMapper 流程:

  • 1. LocalDateTime 反序列化是成功的
    debug

  • 2. 在调用 set 方法时失败了
    debug2
    最终原因在这里: 该方法入参为 int,因为这个方法当时为了方便设置过期时间添加的, 与规范的 setter 方法是重载关系, 我用的是 lombok @setter 方式, 因于字段 expireTimeset 方法重名, lombok 就不生成 setter 方法了. 感觉找到原因了, 手动添加 expireTimeset 方法, 应该可以解决问题了, 结果还是我太年轻了:)

  • 3. 手动添加 expireTime set 方法, 还是反序列化失败
@Getter
@Setter
@ToString
public class ValidateCode implements Serializable {

    // ... 略

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss SSS", locale = "zh", timezone = "GMT+8")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    private LocalDateTime expireTime;

    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }

    public void setExpireTime(int expireIn) {
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }

    /... 略
}

三 分析因 setExpireTime(obj) 方法重载反序列化失败的原因:

debug3
debug4

解决方案: 因为知道了具体的问题所在, 就更容易解决了

  • 第一种方案(推荐): Jackson 注释放到 setExpireTime(..) 方法上.

    @Getter
    @Setter
    @ToString
    public class ValidateCode implements Serializable {
    
      // ... 略
    
      private LocalDateTime expireTime;
    
      @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss SSS", locale = "zh", timezone = "GMT+8")
      @JsonDeserialize(using = LocalDateTimeDeserializer.class)
      public void setExpireTime(LocalDateTime expireTime) {
          this.expireTime = expireTime;
      }
    
      public void setExpireTime(int expireIn) {
          this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
      }
    
      /... 略
    }
    
  • 第二种方案: 重命名重载的方法.

    @Getter
    @Setter
    @ToString
    public class ValidateCode implements Serializable {
    
      // ... 略
    
       @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss SSS", locale = "zh", timezone = "GMT+8")
      @JsonDeserialize(using = LocalDateTimeDeserializer.class)
      private LocalDateTime expireTime;
    
      public void setExpireTime(LocalDateTime expireTime) {
          this.expireTime = expireTime;
      }
    
      public void setExpireIn(int expireIn) {
          this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
      }
    
      /... 略
    }
    

五 想法: 能否提个 PR 给 Jackson 解决这个方法重载的问题

PR

哎, 想的挺美, 想法于现实总是相左.

正文到此结束
本文目录