再也不敢精通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,咱们下期见😋

目录
相关文章
|
1月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
idea按住alt + insert 没有出现get和set方法怎样解决
idea按住alt + insert 没有出现get和set方法怎样解决
|
1月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
26天前
|
API
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
|
1月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
1月前
|
Java Linux 开发者
|
1月前
|
存储 Java
java集合框架复习----(3)Set
这篇文章详细介绍了Java集合框架中的Set集合,包括HashSet和TreeSet的特点、实现原理和使用示例,展示了Set集合的无序性、元素唯一性以及如何通过自定义比较器实现元素的排序。
|
27天前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Azure Cache for Redis 是否记录具体读/写(Get/Set)或删除(Del)了哪些key呢?
【Azure Redis 缓存】Azure Cache for Redis 是否记录具体读/写(Get/Set)或删除(Del)了哪些key呢?
|
29天前
|
存储 Java 索引
|
30天前
|
安全 Java 数据库连接

热门文章

最新文章