再也不敢精通Java了——get/set篇

简介: get,set 原来这么难的

小伙伴们好呀,今天 4ye 来和大家分享在项目中遇到的一个特别有意思的 ‘bug’ 😄

请看~

import lombok.Data;
@Data
public class UserDTO {
    private String uName;
    private boolean active;
    private Boolean closed;
    private Boolean isDeleted;
    private boolean isActive2;
}

上面的这个 DTO 中,生成的 get/set 方法是啥样子的呢?(注意是 lombok 生成的

杰瑞疑惑.png

比如

  1. 是 getUName 还是 getuName
  2. 是 getActive 还是 isActive
  3. 是 getClosed 还是 isClosed
  4. 是 getIsDeleted 还是 isDeleted
  5. 是 getIsActive2 还是 isActive2

上面是 get 的情况,那 set 呢?

请思考下,接下来的答案可能会和你想的有点出入~

你不对劲.png


答案如下

代码1.png

吃惊.png

是不是有点吃惊 哈哈

先来点简单的~

Boolean

这个就很简单啦,生成的都是我们我们平时用到的样子,过~

坚果无语.png

boolean

这个 active 是基本数据类型的 boolean ,生成的 get 方法是 isActive ,  set 方法是 setActive ,很正常🐖

但是你会发现这个 boolean isActive2 很不一样,它生成的 get 方法是 isActive2 , set 方法是 setActive2

按理来说应该生成 isIsActive2 方法和 setIsActive2 方法才对呀,结果居然没有!

请问:你觉得这个是 lombok 的锅还是 java 本身的设计 🐷

为了排除嫌疑,我用 idea 自动生成 get/set ,结果它俩居然是一样的,那这个应该就是 java 的某种特点

代码2.png

不知道小伙伴们还记得 阿里的Java开发手册 没,里面就提到了不要用这个 is 前缀去修饰 pojo 中的 boolean 变量。

阿里Java规范之is.png

不过应该也很少人在这个 pojo 中定义 boolean 类型了叭~  这个也在 手册中有提到 ,毕竟 null 也的话还能表示数据接受的异常等

阿里Java规范之pojo.png

String uName

从上面可以发现,lombok 生成的是 getUName 和 setUName ,而如果通过 IDEA 去生成的话,是生成这个 getuName 和 setuName 。

咩咩咩.png

请先记住这个点,下面正片开始~

代码3.png

如图所示,这个就是折磨了我快一天的 bug,测试接口时,发现了这么诡异的一幕,后端只定义了这个 tDate 属性,压根就没有 tdate 这个属性,可是前端 post 数据时,居然给我传了这两个参数上来,而且诡异的是,我后端还接受不到!

我当时就懵了,想着这前端写的啥代码,怎么给我搞这出…… 🐷

于是乎,我们愉快的进行了沟通~

2000years.png

结果发现,这个是在更新数据时出现的,而这个 tdate 属性是我传回来的,而且就是 null

狗子汗颜.png

我仔细看了下,发现这居然是真的,我的天,我后台明明没有这个 tdate  的!

于是乎,我开始了 扒源码 之路 (就那种直接怼 很笨的做法😅)

直接从 tomcat  到这个 SpringMVC ,最后看到这个 Jackson 时才醒悟过来 (惊呼:我在干什么!🐖)

原理图

web序列化和反序列化图.png

如图 ,后端接收到 request 请求时,要将数据进行 反序列化,转换成我们接口中使用的对象。

您猜怎么着,这反序列化的过程,居然不是直接使用我们定义好的属性字段,而是通过 get/set 方法去推测出来的!!

汤姆震惊.png

这个过程比较复杂,先来看这个请求数据 👇

代码4.png

这里切入有点唐突~  因为这个 debug 过程很长,我也记不住,就记住下面这些要点。🐖

请求过程

请求时,会来到这么一个方法,而在进入这个 _addMethods 方法时,这里还是正常的五个属性

代码5.png

进入之后,会调用到这个方法 legacyManglePropertyName ,最后会返回这个  uname 属性名字(后面再解释)

6.png

出来后,这个 props 直接变成下面 7 个了,包括这个 isActive2 直接变成 active2 属性。

7.png

接下来的一步,就是执行上面的这个 _removeUnwantedProperties 方法,它会移除不想要的属性。(指上面 _addFields 和 _addMethods 推测出来的属性和方法中,所有 isVisible 值为 false 的会被移除掉

8.png

  • 执行  _removeUnwantedAccessor  去移除 不需要的 get/set 方法
  • 执行这个  _renameProperties 方法。这个会根据我们使用的 注解 @JsonProperty("uName") 来重命名我们的这个属性。

执行到最后,会变成这样子,方法名字还是 getUNAME/setUNAME , 但是我们这个属性名字却是 uname

关键点

省略一大堆步骤……(怎么提取请求中的body,并获取其中的字段,匹配到相应的请求参数中 等),直接来到关键点这个 反序列化的赋值操作 ,可以看到这里会将我们的 json 请求中的字段提取出来,然后进行匹配,找不到的话,就无法赋值。

这里面还使用了这个 散列数组 _hashArea 来存储这个属性  。

9.png

这里已经匹配不上了,所以这个我们的 DTO 中获取不到值

效果如下 👇

10.png


响应过程

这里就涉及到这个序列化的过程了, 这个 debug 起来也比较简单了 就不过的赘述啦~

11.png

反序列化时会执行到一个 serializeValue 方法 ,会执行到一个 serializeFields 方法 (将字段进行序列化)

12.png

_props 对应的五个属性如下 👇

13.png

很明显这个 uname 就从这里出现的,最后得到的结果就如下了 😅

14.png

解决办法也很简单,就是用  @JsonProperty("uName") 去定义好这个 属性名称就好了

思考

到这里,我们就简单了解了这个 请求怎么反序列化成为一个对象,以及对象怎么序列化,对客户端进行响应的一个过程

同时我们也了解到 Jackson 有它自己的获取属性的规则,会将我们的 uName 变成这个 uname

参考上面的这个  legacyManglePropertyName 方法了 👇 (这个在  jackson-databind-2.12.4.jar 版本中,之前2.11的代码是用到那个 BeanUtil 包下的,小伙伴们可以自己看看,不过现在标记为 过期的 了。)

15.png


那么 ,lombok 怎么生成这个 get 方法呢?

这里参考下这篇文章 ,了解下 lombok 的工作原理

https://www.cnblogs.com/heyonggang/p/8638374.html

Lombok原理.png

那个语法树啥的我也没有试过~,感觉不懂的地方又多了亿点点

不.png

不过根据文章给出的信息,我们知道 在 lombok 的源码中有很多 Handle 专门来处理每一个 lombok 注解,如下(源码直接在 github 上下载)

16.png

生成 get 方法解密 ,可以看到在源码中,有个很显眼的 toGetterName 方法,

17.png

它会去调用这个 toAccessorName 方法,可以看到这里传了一个 get 前缀字符串

18.png

最后会来到这个 buildAccessorName 方法,没猜错的话,这里就是真正创建的方法了。

19.png

果然,可以看到如下代码 ,capitalize  翻译过来就是 把……首字母大写 (那应该没找错了~)

20.png

最后,来到这个 CapitalizationStrategy 枚举类中,发现默认用了这 BASIC ,把其中的方法拷贝出来运行下,就可以证实我们的猜测了

21.png

代码如下

// BASIC
public String capitalize(String in) {
    if (in.length() == 0) return in;
    char first = in.charAt(0);
    if (!Character.isLowerCase(first)) return in;
    boolean useUpperCase = in.length() > 2 &&
            (Character.isTitleCase(in.charAt(1)) || Character.isUpperCase(in.charAt(1)));
    return (useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first)) + in.substring(1);
}
// BEANSPEC
public String capitalize2(String in) {
    if (in.length() == 0) return in;
    char first = in.charAt(0);
    if (!Character.isLowerCase(first) || (in.length() > 1 && Character.isUpperCase(in.charAt(1)))) return in;
    boolean useUpperCase = in.length() > 2 && Character.isTitleCase(in.charAt(1));
    return (useUpperCase ? Character.toUpperCase(first) : Character.toTitleCase(first)) + in.substring(1);
}
@Test
void testName(){
    System.out.println(capitalize("tDate"));         // TDATE
    System.out.println(capitalize2("tDate"));        // tdate
}

总结

阅读完后,希望你能记住以下几点~

一. 属性名称一定不要弄成有歧义的那种,不然我们都猜不透这个 get/set 是什么样子的!比如 uName 这种第二个字母就大写的!

二. 如果非要写成 uName ,建议自己手写 get/set 或者 使用 @JsonProperty 注解。

三. Jackson 是从get,set方法中推测属性的

四. 使用到 Lombok 相关注解时,它会在编译期根据自己的规则帮我们生成 get/set 方法。

扩展

一. 在阅读 Jackson 源码时,发现它使用到这个 LRUMap  ,会推测第一次请求到的对象属性,并缓存到 props 中,最多存 2000 个。

二. Java 中有一个 Introspector 类,这个和 JavaBean 的规范有关 ,地址 https://www.oracle.com/java/technologies/javase/javabeans-spec.html

(我晕了 😵)

java-introspector.png

这个方法的作用是 使首字母变小 ,而且在 Spring 的这些包中使用到!貌似也是用来推测属性,小伙伴们可以自行研究~

22.png

三. 一开始我以为是 bug,结果来到 Jackson 的 GitHub issue 地址 ,却发现这个 19 年就有了 天呐,早知道我就直接搜 bug 好了,损失了一个 PR 和亿点点时间 🐖,不过也是在这里了解到上面那个 Introspector  的 😂 (好复杂)

https://github.com/FasterXML/jackson-databind/issues/2327

jackson-get set issue.png

最后

感谢各位的阅读,我是 4ye,咱们下期见😋

目录
相关文章
|
2月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
51 6
|
2月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
37 2
|
2月前
|
Java
在Java的世界里,Set只接纳独一无二的元素。
【10月更文挑战第16天】在Java的世界里,Set只接纳独一无二的元素。本文通过拟人化的手法,讲述了重复元素从初次尝试加入Set被拒绝,到经历挣扎、反思,最终通过改变自己,成为独特个体并被Set接纳的全过程。示例代码展示了这一过程的技术实现。
26 1
|
29天前
|
JSON Java 关系型数据库
Java更新数据库报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
在Java中,使用mybatis-plus更新实体类对象到mysql,其中一个字段对应数据库中json数据类型,更新时报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
41 4
Java更新数据库报错:Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'.
|
1月前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java的集合框架中,Set接口以其“无重复”特性著称。本文解析了Set的实现原理,包括HashSet和TreeSet的不同数据结构和算法,以及如何通过示例代码实现最佳实践。选择合适的Set实现类和正确实现自定义对象的hashCode()和equals()方法是关键。
31 4
|
1月前
|
Java
那些与Java Set擦肩而过的重复元素,都经历了什么?
在Java的世界里,Set如同一位浪漫而坚定的恋人,只对独一无二的元素情有独钟。重复元素虽屡遭拒绝,但通过反思和成长,最终变得独特,赢得了Set的认可。示例代码展示了这一过程,揭示了成长与独特性的浪漫故事。
21 4
|
1月前
|
Java 开发者
Java Set:当“重复”遇见它,秒变“独宠”!
在Java编程中,Set接口确保集合中的元素不重复,每个元素都是独一无二的“独宠”。本文介绍了Set的两种常见实现:HashSet和TreeSet。HashSet基于哈希表实现,提供高效的添加、删除和查找操作;TreeSet基于红黑树实现,不仅去重还能对元素进行排序。通过示例代码,展示了这两种集合的具体应用,帮助开发者更好地理解和使用Set。
25 4
|
1月前
|
Java Windows
IDEA不使用lombok,如何快速生成get和set方法
【11月更文挑战第10天】在 IntelliJ IDEA 中生成 `get` 和 `set` 方法有多种方式:通过菜单操作、使用快捷键或自定义模板。菜单操作包括选择“Code”菜单中的“Generate...”,快捷键为“Alt + Insert”。自定义模板可在“File”->“Settings”->“Editor”->“Code Style”->“Java”中设置。批量生成时,可多选变量一次性生成。
|
1月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
1月前
|
存储 Java 开发者
Java 中 Set 类型的使用方法
【10月更文挑战第30天】Java中的`Set`类型提供了丰富的操作方法来处理不重复的元素集合,开发者可以根据具体的需求选择合适的`Set`实现类,并灵活运用各种方法来实现对集合的操作和处理。
下一篇
DataWorks