<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的@Builder不好用,試試@SuperBuilder吧!

          共 5305字,需瀏覽 11分鐘

           ·

          2021-01-07 23:07

          相信Lombok插件大家一定不會(huì)陌生,一個(gè)常用的注解是:@Builer, 它可以幫我們快速實(shí)現(xiàn)一個(gè)builder模式。以常見(jiàn)的商品模型為例:
          @Builder@AllArgsConstructor@NoArgsConstructor@Datapublic class ItemDTO {    /**     * 商品ID     */    private Long itemId;    /**     * 商品標(biāo)題     */    private String itemTitle;    /**     * 商品原價(jià),單位是分     */    private Long price;    /**     * 商品優(yōu)惠價(jià),單位是分     */    private Long promotionPrice;}

          一行代碼就可以構(gòu)造出一個(gè)新的商品:

          ItemDTO itemDTO = ItemDTO.builder()        .itemId(6542744309L)        .itemTitle("測(cè)試請(qǐng)不要拍小番茄500g/盒")        .price(500L)        .promotionPrice(325L)        .build();System.out.println(itemDTO);

          這樣寫不但美觀,而且還會(huì)省去好多無(wú)用的代碼。

          Builder注解的使用限制

          當(dāng)我們的實(shí)體對(duì)象有繼承的設(shè)計(jì)的時(shí)候,Builder注解就沒(méi)那么好用了,還是以商品實(shí)體為例,如果現(xiàn)在商品類都繼承自一個(gè)BaseDTO

          @Builder@NoArgsConstructorpublic class BaseDTO {    /**     * 業(yè)務(wù)身份     */    private String bizType;    /**     * 場(chǎng)景     */    private String scene;}

          這時(shí)候我們?cè)偈褂?/span>Builder注解就會(huì)發(fā)現(xiàn),在子類中無(wú)法通過(guò)builder方法構(gòu)造父類中的成員變量

          image-20210103161952259

          BaseDTO上加上Builder注解也不會(huì)有任何效果。事實(shí)上,Builder注解只管承接注解的這個(gè)類,而不會(huì)管他的父類或者子類。如果真的是這樣的話,遇到有繼承的類,只好又打回原形,寫一堆的setter方法了。

          試試SuperBuilder

          這個(gè)問(wèn)題在lombokv1.18.2版本之前其實(shí)很難辦,但是在這個(gè)版本官方引入了一個(gè)新的注解@SuperBuilder,無(wú)法build父類的問(wèn)題迎刃而解

          The @SuperBuilder annotation produces complex builder APIs for your classes. In contrast to @Builder, @SuperBuilder also works with fields from superclasses. However, it only works for types. Most importantly, it requires that all superclasses also have the @SuperBuilder annotation.

          按照官方文檔的說(shuō)法,為了能夠使用build方法,只需要在子類和父類上都加@SuperBuilder注解,我們?cè)囈幌?/span>

          image-20210103164125460

          果然現(xiàn)在就可以在子類的實(shí)例中build`父類的成員變量了

          Lombok的原理

          Lombok自動(dòng)生成代碼的實(shí)現(xiàn)也是依賴于JVM開(kāi)放的擴(kuò)展點(diǎn),使其可以在編譯的時(shí)候修改抽象語(yǔ)法樹(shù),從而影響最終生成的字節(jié)碼

          圖片來(lái)源地址:

          http://notatube.blogspot.com/2010/12/project-lombok-creating-custom.html

          為什么Builder不能處理父類的成員變量

          我們可以翻一下Lombok的源碼,Lombok對(duì)所有的注解都有兩套實(shí)現(xiàn),javaceclipse,由于我們的運(yùn)行環(huán)境是Idea所以我們選擇javac的實(shí)現(xiàn),javac版本的實(shí)現(xiàn)在lombok.javac.handlers.HandleBuilder#handle這個(gè)方法中

          JavacNode parent = annotationNode.up();if (parent.get() instanceof JCClassDecl) {   job.parentType = parent;   JCClassDecl td = (JCClassDecl) parent.get();      ListBuffer allFields = new ListBuffer();   boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent));  // 取出所有的成員變量   for (JavacNode fieldNode : HandleConstructor.findAllFields(parent, true)) {      JCVariableDecl fd = (JCVariableDecl) fieldNode.get();      JavacNode isDefault = findAnnotation(Builder.Default.class, fieldNode, false);      boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode));      // 巴拉巴拉,省略掉}


          這里的annotationNode就是Builder注解,站在抽象語(yǔ)法樹(shù)的角度,調(diào)用up方法得到的就是被注解修飾的類,也就是需要生成builder方法的類。

          通過(guò)查看源代碼,@Builder注解是可以修飾類,構(gòu)造函數(shù)和方法的,為了簡(jiǎn)單起見(jiàn),上面的代碼只截取了@Builder修飾類這一種情況,這段代碼關(guān)鍵的地方就在于調(diào)用HandleConstructor.findAllFields方法獲得類中所有的成員變量:

          public static List findAllFields(JavacNode typeNode, boolean evenFinalInitialized) {   ListBuffer fields = new ListBuffer();  // 從抽象語(yǔ)法樹(shù)出發(fā),遍歷類的所有的成員變量   for (JavacNode child : typeNode.down()) {      if (child.getKind() != Kind.FIELD) continue;      JCVariableDecl fieldDecl = (JCVariableDecl) child.get();      //Skip fields that start with $      if (fieldDecl.name.toString().startsWith("$")) continue;      long fieldFlags = fieldDecl.mods.flags;      //Skip static fields.      if ((fieldFlags & Flags.STATIC) != 0) continue;      //Skip initialized final fields      boolean isFinal = (fieldFlags & Flags.FINAL) != 0;      if (evenFinalInitialized || !isFinal || fieldDecl.init == null) fields.append(child);   }   return fields.toList();}

          這段代碼比較簡(jiǎn)單,就是對(duì)類中的成員變量做了過(guò)濾,比如說(shuō),靜態(tài)變量就不能被@Builder方法構(gòu)造。有一個(gè)有意思的點(diǎn),盡管$可以合法的出現(xiàn)在java的變量命名中,但是Lombok對(duì)這種變量做了過(guò)濾,因此變量名以$開(kāi)始的也不能被@Builder構(gòu)造,經(jīng)過(guò)我們的驗(yàn)證確實(shí)是這樣的。

          如果我們用JDT AstView看一下ItemDTO的抽象語(yǔ)法樹(shù)結(jié)構(gòu),發(fā)現(xiàn)Java的抽象語(yǔ)法樹(shù)設(shè)計(jì)的確是每個(gè)類只包含顯式聲明的變量而不包括父類的成員變量(該插件支持點(diǎn)擊語(yǔ)法樹(shù)節(jié)點(diǎn)可以和源文件聯(lián)動(dòng),且數(shù)量只有4個(gè)和ItemDTO聲明的成員變量數(shù)量一致)

          image-20210104000114524

          因?yàn)?/span>findAllFields方法是從當(dāng)前類的抽象語(yǔ)法樹(shù)出發(fā)去找所有的成員變量,所以就只能找到當(dāng)前類的成員變量,而訪問(wèn)不到父類的成員變量

          一個(gè)鏡像的問(wèn)題就是,既然@Builder注解不能構(gòu)造父類的成員變量,那@SuperBuilder是怎么做到的呢?翻一下@SuperBuilder的源碼,核心邏輯在lombok.javac.handlers.HandleSuperBuilder#handle

          // 巴拉巴拉省略JCClassDecl td = (JCClassDecl) parent.get();// 獲取繼承的父類的抽象語(yǔ)法樹(shù)JCTree extendsClause = Javac.getExtendsClause(td);JCExpression superclassBuilderClass = null;if (extendsClause instanceof JCTypeApply) {   // Remember the type arguments, because we need them for the extends clause of our abstract builder class.   superclassTypeParams = ((JCTypeApply) extendsClause).getTypeArguments();   // A class name with a generics type, e.g., "Superclass".   extendsClause = ((JCTypeApply) extendsClause).getType();}if (extendsClause instanceof JCFieldAccess) {   Name superclassName = ((JCFieldAccess) extendsClause).getIdentifier();   String superclassBuilderClassName = superclassName.toString() + "Builder";   superclassBuilderClass = parent.getTreeMaker().Select((JCFieldAccess) extendsClause, parent.toName(superclassBuilderClassName));} else if (extendsClause != null) {   String superclassBuilderClassName = extendsClause.toString() + "Builder";   superclassBuilderClass = chainDots(parent, extendsClause.toString(), superclassBuilderClassName);}// 巴拉巴拉省略

          可以看到,這里拿到了繼承的父類的抽象語(yǔ)法樹(shù),并在后面的邏輯中進(jìn)行了處理,這里不再贅述


          - END -

          Arthas原理系列(一):利用JVM的attach機(jī)制實(shí)現(xiàn)一個(gè)極簡(jiǎn)的watch命令

          Arthas原理系列(二):總體架構(gòu)和項(xiàng)目入口

          Arthas原理系列(三):服務(wù)端啟動(dòng)流程

          Arthas原理系列(四):字節(jié)碼插裝讓一切變得有可能

          Arthas原理系列(五):watch命令的實(shí)現(xiàn)原理

          掃描二維碼關(guān)注

          我領(lǐng)取面試資料


          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  色老板网址 | 四色成人AV永久网址 | 色青娱乐 | 亚洲无码在线播放视频 | 91在线精品秘 一区二区 |