<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 5305字,需瀏覽 11分鐘

           ·

          2022-01-01 02:05

          點擊關注公眾號,Java干貨及時送達

          小伙伴們好呀,今天 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 生成的

          image-20211216074356505

          比如

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

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

          請思考下,接下來的答案可能會和你想的有點出入~


          答案如下

          lombok版

          是不是有點吃驚 哈哈

          先來點簡單的~

          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ù) ??

          發(fā)出的請求

          這里切入有點唐突~ ?因為這個 debug 過程很長,我也記不住,就記住下面這些要點。??

          請求過程

          請求時,會來到這么一個方法,而在進入這個 _addMethods 方法時,這里還是正常的五個屬性

          image-20211219220118077

          進入之后,會調(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

          最后

          終于寫完這篇文章了,吸收了好多奇奇怪怪的知識點,這 Java 也太復雜了叭!幸好畢業(yè)那會寫過精通兩字,不虧 哈哈哈? 從今以后都不敢寫了 ,一個 get,set 都這么難~?



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

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧美黄片 | 中文电视剧字幕在线播放网站 | 国产精品尤物视频 | 亚洲淫淫网| 豆花视频在线看成人网站 |