再也不敢精通Java了——get/set篇
點擊關注公眾號,Java干貨及時送達
看題
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 生成的)

比如
是 getUName 還是 getuName 是 getActive 還是 isActive 是 getClosed 還是 isClosed 是 getIsDeleted 還是 isDeleted 是 getIsActive2 還是 isActive2
上面是 get 的情況,那 set 呢?
請思考下,接下來的答案可能會和你想的有點出入~

答案如下


是不是有點吃驚 哈哈
先來點簡單的~
Boolean
這個就很簡單啦,生成的都是我們我們平時用到的樣子,過~

boolean
這個 active 是基本數(shù)據(jù)類型的 boolean ,生成的 get 方法是 isActive , ?set 方法是 setActive ,很正常??
但是你會發(fā)現(xiàn)這個 boolean isActive2 很不一樣,它生成的 get 方法是 isActive2 , set 方法是 setActive2 。
按理來說應該生成 isIsActive2 方法和 setIsActive2 方法才對呀,結果居然沒有!
請問:你覺得這個是 lombok 的鍋還是 java 本身的設計 ??
為了排除嫌疑,我用 idea 自動生成 get/set ,結果它倆居然是一樣的,那這個應該就是 java 的某種特點 了

不知道小伙伴們還記得 阿里的Java開發(fā)手冊 沒,里面就提到了不要用這個 is 前綴去修飾 pojo 中的 boolean 變量。

不過應該也很少人在這個 pojo 中定義 boolean 類型了叭~ ?這個也在 手冊中有提到 ,畢竟 null 也的話還能表示數(shù)據(jù)接受的異常等

String uName
從上面可以發(fā)現(xiàn),lombok 生成的是 getUName 和 setUName ,而如果通過 IDEA 去生成的話,是生成這個 getuName 和 setuName 。

請先記住這個點,下面正片開始~

如圖所示,這個就是折磨了我快一天的 bug,測試接口時,發(fā)現(xiàn)了這么詭異的一幕,后端只定義了這個 tDate 屬性,壓根就沒有 tdate 這個屬性,可是前端 post 數(shù)據(jù)時,居然給我傳了這兩個參數(shù)上來,而且詭異的是,我后端還接受不到!
我當時就懵了,想著這前端寫的啥代碼,怎么給我搞這出…… ??
于是乎,我們愉快的進行了溝通~

結果發(fā)現(xiàn),這個是在更新數(shù)據(jù)時出現(xiàn)的,而這個 tdate 屬性是我傳回來的,而且就是 null

我仔細看了下,發(fā)現(xiàn)這居然是真的,我的天,我后臺明明沒有這個 tdate ?的!
于是乎,我開始了 扒源碼 之路 (就那種直接懟 很笨的做法??)
直接從 tomcat ?到這個 SpringMVC ,最后看到這個 Jackson 時才醒悟過來 (驚呼:我在干什么!??)
原理圖

如圖 ,后端接收到 request 請求時,要將數(shù)據(jù)進行 反序列化,轉換成我們接口中使用的對象。
您猜怎么著,這反序列化的過程,居然不是直接使用我們定義好的屬性字段,而是通過 get/set 方法去推測出來的?。?/strong>

這個過程比較復雜,先來看這個請求數(shù)據(jù) ??

這里切入有點唐突~ ?因為這個 debug 過程很長,我也記不住,就記住下面這些要點。??
請求過程
請求時,會來到這么一個方法,而在進入這個 _addMethods 方法時,這里還是正常的五個屬性

進入之后,會調(diào)用到這個方法 legacyManglePropertyName ,最后會返回這個 ?uname 屬性名字(后面再解釋)

出來后,這個 props 直接變成下面 7 個了,包括這個 isActive2 直接變成 active2 屬性。

接下來的一步,就是執(zhí)行上面的這個 _removeUnwantedProperties 方法,它會移除不想要的屬性。(指上面 _addFields 和 _addMethods 推測出來的屬性和方法中,所有 isVisible 值為 false 的會被移除掉 )

執(zhí)行 ?_removeUnwantedAccessor ?去移除 不需要的 get/set 方法 執(zhí)行這個 ?_renameProperties 方法。這個會根據(jù)我們使用的 注解 @JsonProperty("uName") 來重命名我們的這個屬性。
執(zhí)行到最后,會變成這樣子,方法名字還是 getUNAME/setUNAME , 但是我們這個屬性名字卻是 uname
關鍵點
省略一大堆步驟……(怎么提取請求中的body,并獲取其中的字段,匹配到相應的請求參數(shù)中 等),直接來到關鍵點這個 反序列化的賦值操作 ,可以看到這里會將我們的 json 請求中的字段提取出來,然后進行匹配,找不到的話,就無法賦值。
這里面還使用了這個 散列數(shù)組 _hashArea 來存儲這個屬性 ?。

這里已經(jīng)匹配不上了,所以這個我們的 DTO 中獲取不到值
效果如下 ??

響應過程
這里就涉及到這個序列化的過程了, 這個 debug 起來也比較簡單了 就不過的贅述啦~

反序列化時會執(zhí)行到一個 serializeValue 方法 ,會執(zhí)行到一個 serializeFields 方法 (將字段進行序列化)

_props 對應的五個屬性如下 ??

很明顯這個 uname 就從這里出現(xiàn)的,最后得到的結果就如下了 ??

解決辦法也很簡單,就是用 ?@JsonProperty("uName") 去定義好這個 屬性名稱就好了 。
思考
到這里,我們就簡單了解了這個 請求怎么反序列化成為一個對象,以及對象怎么序列化,對客戶端進行響應的一個過程。
同時我們也了解到 Jackson 有它自己的獲取屬性的規(guī)則,會將我們的 uName 變成這個 uname
參考上面的這個 ?legacyManglePropertyName 方法了 ?? (這個在 ?jackson-databind-2.12.4.jar 版本中,之前2.11的代碼是用到那個 BeanUtil 包下的,小伙伴們可以自己看看,不過現(xiàn)在標記為 過期的 了。)

那么 ,lombok 怎么生成這個 get 方法呢?
這里參考下這篇文章 ,了解下 lombok 的工作原理
https://www.cnblogs.com/heyonggang/p/8638374.html

那個語法樹啥的我也沒有試過~,感覺不懂的地方又多了億點點

不過根據(jù)文章給出的信息,我們知道 在 lombok 的源碼中有很多 Handle 專門來處理每一個 lombok 注解,如下(源碼直接在 github 上下載)

生成 get 方法解密 ,可以看到在源碼中,有個很顯眼的 toGetterName 方法,

它會去調(diào)用這個 toAccessorName 方法,可以看到這里傳了一個 get 前綴字符串

最后會來到這個 buildAccessorName 方法,沒猜錯的話,這里就是真正創(chuàng)建的方法了。

果然,可以看到如下代碼 ,capitalize ?翻譯過來就是 把……首字母大寫 (那應該沒找錯了~)

最后,來到這個 CapitalizationStrategy 枚舉類中,發(fā)現(xiàn)默認用了這 BASIC ,把其中的方法拷貝出來運行下,就可以證實我們的猜測了

代碼如下
//?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 相關注解時,它會在編譯期根據(jù)自己的規(guī)則幫我們生成 get/set 方法。
擴展
一. 在閱讀 Jackson 源碼時,發(fā)現(xiàn)它使用到這個 LRUMap ?,會推測第一次請求到的對象屬性,并緩存到 props 中,最多存 2000 個。
二. Java 中有一個 Introspector 類,這個和 JavaBean 的規(guī)范有關 ,地址 https://www.oracle.com/java/technologies/javase/javabeans-spec.html
(我暈了 ??)

這個方法的作用是 使首字母變小 ,而且在 Spring 的這些包中使用到!貌似也是用來推測屬性,小伙伴們可以自行研究~

三. 一開始我以為是 bug,結果來到 Jackson 的 GitHub issue 地址 ,卻發(fā)現(xiàn)這個 19 年就有了 天吶,早知道我就直接搜 bug 好了,損失了一個 PR 和億點點時間 ??,不過也是在這里了解到上面那個 Introspector ?的 ?? (好復雜)
https://github.com/FasterXML/jackson-databind/issues/2327

最后
往 期 推 薦
1、Log4j2維護者吐槽沒工資還要挨罵,GO安全負責人建議開源作者向公司收費 2、太難了!讓程序員崩潰的8個瞬間 3、2021年程序員們都在用的神級數(shù)據(jù)庫 4、Windows重要功能被閹割,全球用戶怒噴數(shù)月后微軟終于悔改 5、牛逼!國產(chǎn)開源的遠程桌面火了,只有9MB 支持自建中繼器! 6、摔到老三的 Java,未來在哪? 7、真香!用 IDEA 神器看源碼,效率真高! 點分享
點收藏
點點贊
點在看





