SpringMvc 与 Lombok 碰撞导致 JSON 反序列化失败

SpringMvc 与 Lombok 中 JSON 反序列化失败

错误复现_1

@Data
public class User{
    private Long id;
    private boolean isOk;
}

@RequestMapping
public R<User> getUser(@RequestBody User user){
    return R.success(user);
}

// 前端传参 - {"id": 123456789,"isOk": true}
// 后端返回 - {"id": 123456789,"isOk": false}

排查分析_1

  1. 查看 User 类 class 文件。

    public class User{
        pirvate Long id;
        private boolean isOk;
        public User() {}
        public Long getId() { return this.id; }
        public boolean isOk() { return this.isOk; }
        public void setId(final Long id) { this.id = id; }
        public void setOk(final boolean isOk) { this.isOk = isOk; }
    }
    
  2. 提出猜测:Lombok 对于 boolean 类型生成的 Getter/Setter 方法与其他类型不同,该原因导致的数据不一致

错误复现_2

@Data
public class User{
    private Long id;
    private String Name;
    private Long cId;
}

// 前端传参 - {"id": 1,"Name": "LXL","cId": 2}

@RequestMapping
public void getUser(@RequestBody User user){
    System.out.println(user.getId()); // 1
    System.out.println(user.getName()); // null
    System.out.println(user.getCId()); // null
}

排查分析_2

  1. 查看 User 类 class 文件。

    public class User {
        private Long id;
        private String Name;
        private Long cId;
        public User() {}
        public Long getId() { return this.id; }
        public String getName() { return this.Name; }
        public Long getCId() { return this.cId; }
        public void setId(final Long id) { this.id = id; }
        public void setName(final String Name) { this.Name = Name; }
        public void setCId(final Long cId) { this.cId = cId; }
    }
    
  2. 提出猜测:反序列化时,由于某种原因导致无法正常赋值。

揭开谜团

结合上述两个猜测,去了解了 SpringMvc 的反序列化机制、Lombok 的代码生成机制。

先说一下 SpringMvc 的 @RequestBody 注解:

  1. SpringMvc 中通过 @RequestBody 注解实现将 json 数据转成 java 对象。
  2. 处理 @RequestBody 注解时,内部使用 jackson 框架完成该反序列化过程。

接下来是 Jackson 框架的反序列化操作步骤:

  1. 仅有全参数构造器时,通过调用该构造器,映射 JSON 属性与 Construction 参数列表完成对象的实例化。
  2. 有无参数构造器时,优先使用无参数构造器 + Getter/Setter 方法完成序列化与反序列化。调用反序列化的目标类的无参构造函数,构造一个 java 对象。然后调用该类的成员变量的 set 方法,为该对象的每一个成员变量赋值。
  3. Jackson 遵守了 JavaBean 的规范:
    • 首字母为大写的属性名(如:Name, URL, SuV…),这种属性名直接忽略不注入。
    • 第一个字母是小写,第二个字母大写的情况(如:pId, sUV…),在生成 Getter/Setter 的时,直接在前面加上 set/get,比如 pId 生成 setpId()/getpId(),所以 pId 属性在注入的时候会寻找 setpId() 方法,而不是 setPId() 。

最后是 Lombok 的 Getter/Setter 生成机制:

  1. 当成员变量为 boolean 类型时,属性名为 isXxx 生成的 Getter/Setter 方法的方法名为 isXxx/setXxx。
  2. 其他基本类型与所有引用数据类型,属性名为 aaBb 生成的 Getter/Setter 方法的方法名为 getAaBb/setAaBb,例如 isXxx 生成 getIsXxx/SetIsXxx。

这几个知识点就可以解决错误 1、错误 2 啦!!!

  1. 错误 1 造成原因:为属性名 isOk 查找 Setter 方法时,找不到 setIsOk,所以无法注入,使用默认值 false。
  2. 错误 2 造成原因:属性名 Name 不符合 JavaBean 规范被直接忽略注入;属性名 cId 查找的 Setter 方法名应该为 setcId,而 Lombok 生成的是 setCId,也是注入不了的。

方案与总结

  1. 规范且合理制定成员变量名称。
  2. 尽量使用包装类,既可以符合 Java 语言面向对象的特性,同时可以避免 Lombok 生成的 Setter/Getter 与 Jackson 框架规则间的冲突。
  3. 上述两种方案不适合的话,可以考虑手动生成 Setter/Getter 方法。
  4. 通过 @JsonProperty 显式指定 JSON 属性与 Java 属性的对应关系。

你可能感兴趣的:(Lombok,Spring,json,mvc)