前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 中类似 aBbb 单字母单词序列化与反序列问题(大小写转换异常)

Spring 中类似 aBbb 单字母单词序列化与反序列问题(大小写转换异常)

原创
作者头像
Lorin 洛林
修改2024-04-28 13:29:25
2200
修改2024-04-28 13:29:25
举报
文章被收录于专栏:Java 技术小屋Java 技术小屋

前言

  • 最近在使用 spring boot mvc 实现 HTTP 接口时出现了大小写异常转换的神秘现象,比如下面的案例:
代码语言:java
复制
@Data
public class User {
    private int id;
    private String name;
    private String aTest;
}

请求参数:
{
  "name": "小明",
  "aTest": "测试" 
}

响应参数:
{
  "id": 1,
  "name": "小明",
  "atest": null  // aTest 未成功接收
}

1、前端字段序列化异常

2、aTest 字段被序列化为了 atest

代码准备

  • Spring-boot-parent 2.6.4
代码语言:java
复制
@Data
public class User {
    private int id;
    private String name;
    private String aTest;

    public User(int id, String name, String aTest) {
        this.id = id;
        this.name = name;
        this.aTest = aTest;
    }
}

@Repository
public class UserRepository {
    public User createUser(User user) {
        System.out.println(user);
        return user;
    }
}

@RestController
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userRepository.createUser(user);
    }
}

问题排查

  • 经过一系列排查发现是对象序列化和反序列化导致的问题,一个是使用 lombok 生成 get、set 方法,一个使用自定义生成 get、set 方式实现,下面我们来看一下两种情况的差异:

lombok

  • User 对象使用 lombok 生成 get、set 方法
代码语言:java
复制
@Data
public class User {
    private int id;
    private String name;
    private String aTest;
}
  • 测试结果:
代码语言:txt
复制
POST http://localhost:8080/users
Content-Type: application/json

{
  "name": "小明",
  "aTest": "测试"
}

// 打印日志 aTest 字段未被成功接收
User(id=0, name=小明, aTest=null)

// 响应日志 aTest 字段被转换为 atest
{
  "id": 1,
  "name": "小明",
  "atest": null
}
  • 可以发现接口请求传递过来的 aTest 没有被正常反序列,响应时 aTest 字段被序列为了 atest

自定义生成 get、set

  • 自定义生成 user 对象 get、set 方法。
代码语言:java
复制
public class User {
    private int id;
    private String name;
    private String aTest;

    public User(int id, String name, String aTest) {
        this.id = id;
        this.name = name;
        this.aTest = aTest;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getaTest() {
        return aTest;
    }

    public void setaTest(String aTest) {
        this.aTest = aTest;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", aTest='" + aTest + '\'' +
                '}';
    }
}
  • 测试结果:
代码语言:java
复制
POST http://localhost:8080/users
Content-Type: application/json

{
  "name": "小明",
  "aTest": "测试"
}

// 打印日志 aTest 字段被成功接收
User{id=0, name='小明', aTest='测试'}

// 响应日志 aTest 字段被转为预期的 aTest
{
  "id": 1,
  "name": "小明",
  "aTest": "测试"
}
  • 可以发现请求时 aTest 被正常解析,响应时 aTest 被序列化为预期的 aTest

结合源码解析

  • 这里我们可以对比 lombok 和我们自定义生成 get、set 方法的差异:
代码语言:java
复制
// lombok
    public String getATest() {
        return this.aTest;
    }

    public void setATest(final String aTest) {
        this.aTest = aTest;
    }

// 自定义
    public String getaTest() {
        return aTest;
    }

    public void setaTest(String aTest) {
        this.aTest = aTest;
    }
  • 我们知道 Spring 默认使用 jackson 进行序列化和反序列,在构建 BeanDeserializer 时会通过方法和字段获取对应的 属性properties,由于 Spring 和 lombokJavaBeans 规范的定义理解并不一致导致识别字段结果不同,具体可以参考:https://github.com/projectlombok/lombok/issues/757

使用 lombok

  • 我们先看看,lombok 生成的 BeanDeserializer
  • com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#buildBeanDeserializer 中我们可以看到调用了 buildBeanDeserializer 生成 BeanDeserializer
  • 一直断点,我们可以来到 com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector#collectAll 获取 properties map,这里是重点:
  • 执行 _addFields、_addMethods 我们推断出了来 4 个字段:
  • 这里为什么是4个呢?因为 lombokSpring jacksonJavaBeans 规范的定义理解不一致,导致从方法中推断出了 atest 字段。
  • 然后执行 _removeUnwantedProperties 字段去除了 aTest 字段,因为它是不可见的。

使用 lombok 自定义生成 user 对象 get、set 方法

  • 我们采取自定义的写法生成 user 对象 get、set 方法,对象属性可以正常被识别:

如何解决

  • 知道了问题产生原因,解决就很简单了,只要让我们字段属性被正常识别就可以了。

使用注解 @JsonProperty("aTest")

自定义实现符合 Spring 规范的 get set 方法

自定义实现包含该字段的默认构造函数

代码语言:java
复制
    public User(int id, String name, String aTest) {
        this.id = id;
        this.name = name;
        this.aTest = aTest;
    }

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 代码准备
    • 问题排查
      • lombok
        • 自定义生成 get、set
        • 结合源码解析
          • 使用 lombok
            • 使用 lombok 自定义生成 user 对象 get、set 方法
            • 如何解决
              • 使用注解 @JsonProperty("aTest")
                • 自定义实现符合 Spring 规范的 get set 方法
                  • 自定义实现包含该字段的默认构造函数
                  • 个人简介
                  相关产品与服务
                  云数据库 MySQL
                  腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档