<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>

          記一次使用 Lombok 翻車造成的事故!

          共 5435字,需瀏覽 11分鐘

           ·

          2020-11-27 21:02

          https://juejin.im/post/6881432532332576781

          序言

          去年在項目當中引入了Lombok插件,著實解放了雙手,代替了一些重復的簡單工作(Getter,Setter,toString等方法的編寫)。

          但是,在使用的過程當中,也發(fā)現(xiàn)了一些坑,開始的時候并沒有察覺到是Lombok的問題,后來跟蹤了對應的其他組件的源碼,才發(fā)現(xiàn)是Lombok的問題!

          Setter-Getter方法的坑

          問題發(fā)現(xiàn)

          我們在項目當中主要使用Lombok的Setter-Getter方法的注解,也就是組合注解@Data,但是在一次使用Mybatis插入數(shù)據(jù)的過程當中,出現(xiàn)了一個問題,問題描述如下:

          我們有個實體類:

          @Data
          public?class?NMetaVerify{
          ????private?NMetaType?nMetaType;
          ????private?Long?id;
          ????....其他屬性
          }

          當我們使用Mybatis插入數(shù)據(jù)的時候,發(fā)現(xiàn),其他屬性都能正常的插入,但是就是nMetaType屬性在數(shù)據(jù)庫一直是null.

          解決

          當我debug項目代碼到調(diào)用Mybatis的插入SQL對應的方法的時候,我看到NMetaVerify對象的nMetaType屬性還是有數(shù)據(jù)的,但是執(zhí)行插入之后,數(shù)據(jù)庫的nMetaType字段就是一直是null,原先我以為是我的枚舉類型寫法不正確,看了下別的同樣具有枚舉類型的字段,也是正常能插入到數(shù)據(jù)庫當中的,這更讓我感覺到疑惑了.

          于是,我就跟蹤Mybatis的源碼,發(fā)現(xiàn)Mybatis在獲取這個nMetaType屬性的時候使用了反射,使用的是getxxxx方法來獲取的,但是我發(fā)現(xiàn)nMetaType的get方法好像有點和Mybatis需要的getxxxx方法長的好像不一樣.問題找到了!

          原因

          Lombok對于第一個字母小寫,第二個字母大寫的屬性生成的get-set方法和Mybatis以及idea或者說是Java官方認可的get-set方法生成的不一樣:

          Lombok生成的Get-Set方法

          @Data
          public?class?NMetaVerify?{
          ????private?Long?id;
          ????private?NMetaType?nMetaType;
          ????private?Date?createTime;
          ????
          ????public?void?lombokFound(){
          ????????NMetaVerify?nMetaVerify?=?new?NMetaVerify();
          ????????nMetaVerify.setNMetaType(NMetaType.TWO);?//注意:nMetaType的set方法為setNMetaType,第一個n字母大寫了,
          ????????nMetaVerify.getNMetaType();??????????????????????????????????//getxxxx方法也是大寫
          ????}
          }

          idea,Mybatis,Java官方默認的行為為:

          public?class?NMetaVerify?{
          ????private?Long?id;
          ????private?NMetaType?nMetaType;
          ????private?Date?createTime;

          ????public?Long?getId()?{
          ????????return?id;
          ????}

          ????public?void?setId(Long?id)?{
          ????????this.id?=?id;
          ????}

          ????public?NMetaType?getnMetaType()?{//注意:nMetaType屬性的第一個字母小寫
          ????????return?nMetaType;
          ????}

          ????public?void?setnMetaType(NMetaType?nMetaType)?{//注意:nMetaType屬性的第一個字母小寫
          ????????this.nMetaType?=?nMetaType;
          ????}

          ????public?Date?getCreateTime()?{
          ????????return?createTime;
          ????}

          ????public?void?setCreateTime(Date?createTime)?{
          ????????this.createTime?=?createTime;
          ????}
          }

          Mybatis(3.4.6版本)解析get-set方法獲取屬性名字的源碼:

          package?org.apache.ibatis.reflection.property;

          import?java.util.Locale;

          import?org.apache.ibatis.reflection.ReflectionException;

          /**
          ?*?@author?Clinton?Begin
          ?*/

          public?final?class?PropertyNamer?{

          ?????private?PropertyNamer()?{
          ?????????//?Prevent?Instantiation?of?Static?Class
          ???????}

          ????public?static?String?methodToProperty(String?name)?{
          ??????if?(name.startsWith("is"))?{//is開頭的一般是bool類型,直接從第二個(索引)開始截取(簡單粗暴)
          ??????????name?=?name.substring(2);
          ??????}?else?if?(name.startsWith("get")?||?name.startsWith("set"))?{//set-get的就從第三個(索引)開始截取
          ??????????name?=?name.substring(3);
          ??????}?else?{
          ??????????throw?new?ReflectionException("Error?parsing?property?name?'"?+?name?+?"'.??Didn't?start?with?'is',?'get'?or?'set'.");
          ??????}
          ???????????//下面這個判斷很重要,可以分成兩句話開始解釋,解釋如下
          ????????????//第一句話:name.length()==1
          ????????????//???????對于屬性只有一個字母的,例如private?int?x;
          ????????????//??????????對應的get-set方法是getX();setX(int?x);
          ????????????//第二句話:name.length()?> 1 && !Character.isUpperCase(name.charAt(1)))
          ????????????//??????屬性名字長度大于1,并且第二個(代碼中的charAt(1),這個1是數(shù)組下標)字母是小寫的
          ????????????//??????如果第二個char是大寫的,那就直接返回name
          ??????if?(name.length()?==?1?||?(name.length()?>?1?&&?!Character.isUpperCase(name.charAt(1))))?{
          ??????????name?=?name.substring(0,?1).toLowerCase(Locale.ENGLISH)?+?name.substring(1);//讓屬性名第一個字母小寫,然后加上后面的內(nèi)容
          ??????}

          ??????return?name;
          ????}

          ????public?static?boolean?isProperty(String?name)?{
          ???????return?name.startsWith("get")?||?name.startsWith("set")?||?name.startsWith("is");
          ????}

          ????public?static?boolean?isGetter(String?name)?{
          ???????return?name.startsWith("get")?||?name.startsWith("is");
          ????}

          ????public?static?boolean?isSetter(String?name)?{
          ???????return?name.startsWith("set");
          ????}

          }

          Mybatis解析get-set方法為屬性名字測試

          ????@Test
          ????public?void?foundPropertyNamer()?{
          ????????String?isName?=?"isName";
          ????????String?getName?=?"getName";
          ????????String?getnMetaType?=?"getnMetaType";
          ????????String?getNMetaType?=?"getNMetaType";

          ????????Stream.of(isName,getName,getnMetaType,getNMetaType)
          ????????????????.forEach(methodName->System.out.println("方法名字是:"+methodName+"?屬性名字:"+?PropertyNamer.methodToProperty(methodName)));
          ????}

          輸出結果如下:

          ????方法名字是:isName?屬性名字:name?
          ????方法名字是:getName?屬性名字:name?
          ????方法名字是:getnMetaType?屬性名字:nMetaType?//這個以及下面的屬性第二個字母都是大寫,所以直接返回name
          ????方法名字是:getNMetaType?屬性名字:NMetaType

          解決方案

          1. 修改屬性名字,讓第二個字母小寫,或者說是規(guī)定所有的屬性的前兩個字母必須小寫
          2. 如果數(shù)據(jù)庫已經(jīng)設計好,并且前后端接口對接好了,不想修改,那就專門為這種特殊的屬性使用idea生成get-set方法復制代碼

          @Accessor(chain = true)注解的問題

          問題發(fā)現(xiàn)

          在使用 easyexcel 導出的時候,發(fā)現(xiàn)以前的實體類導出都很正常,但是現(xiàn)在新加的實體類不正常了,比對了發(fā)現(xiàn),新加的實體類增加了@Accessor(chain = true)注解,我們的目的主要是方便我們鏈式調(diào)用set方法:

          new?UserDto()
          .setUserName("")
          .setAge(10)
          ........
          .setBirthday(new?Date());

          原因

          easyexcel底層使用的是cglib來做反射工具包的:

          com.alibaba.excel.read.listener.ModelBuildEventListener?類的第130行
          BeanMap.create(resultModel).putAll(map);

          最底層的是cglib的BeanMap的這個方法調(diào)用

          abstract?public?Object?put(Object?bean,?Object?key,?Object?value);

          但是cglib使用的是Java的rt.jar里面的一個Introspector這個類的方法:

          Introspector.java 第520行

          if?(int.class.equals(argTypes[0])?&&?name.startsWith(GET_PREFIX))?{
          ???pd?=?new?IndexedPropertyDescriptor(this.beanClass,?name.substring(3),?null,?null,?method,?null);
          ???//下面這行判斷,只獲取返回值是void類型的setxxxx方法
          ?}?else?if?(void.class.equals(resultType)?&&?name.startsWith(SET_PREFIX))?{
          ????//?Simple?setter
          ????pd?=?new?PropertyDescriptor(this.beanClass,?name.substring(3),?null,?method);
          ????if?(throwsException(method,?PropertyVetoException.class))?{
          ???????pd.setConstrained(true);
          ????}
          }

          解決方案

          1. 去掉Accessor注解
          2. 要么就等待easyexcel的作者替換掉底層的cglib或者是其他,反正是支持獲取返回值不是void的setxxx方法就行復制代碼。
          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  黄色日本在线观看视频 | 国产女人操逼打咆视频 | 久精品成人免费视频 | 欧美三级一区二区 | 青青五月丁香在线 |