Lombok的@Builder不好使?來試試這個

2021-01-15 Java技術架構

以下文章來源於苦味代碼 ,作者L

相信Lombok插件大家一定不會陌生,一個常用的註解是:@Builer, 它可以幫我們快速實現一個builder模式。以常見的商品模型為例:

@Builder@AllArgsConstructor@NoArgsConstructor@Datapublic class ItemDTO { /** * 商品ID */ private Long itemId; /** * 商品標題 */ private String itemTitle; /** * 商品原價,單位是分 */ private Long price; /** * 商品優惠價,單位是分 */ private Long promotionPrice;}一行代碼就可以構造出一個新的商品:

ItemDTO itemDTO = ItemDTO.builder() .itemId(6542744309L) .itemTitle("測試請不要拍小番茄500g/盒") .price(500L) .promotionPrice(325L) .build();System.out.println(itemDTO);這樣寫不但美觀,而且還會省去好多無用的代碼。

Builder註解的使用限制

當我們的實體對象有繼承的設計的時候,Builder註解就沒那麼好用了,還是以商品實體為例,如果現在商品類都繼承自一個BaseDTO

@Builder@NoArgsConstructorpublic class BaseDTO { /** * 業務身份 */ private String bizType; /** * 場景 */ private String scene;}這時候我們再使用Builder註解就會發現,在子類中無法通過builder方法構造父類中的成員變量

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

試試SuperBuilder吧

這個問題在lombokv1.18.2版本之前其實很難辦,但是在這個版本官方引入了一個新的註解@SuperBuilder,無法build父類的問題迎刃而解

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.

按照官方文檔的說法,為了能夠使用build方法,只需要在子類和父類上都加@SuperBuilder註解,我們試一下

果然現在就可以在子類的實例中build`父類的成員變量了

Lombok的原理

Lombok自動生成代碼的實現也是依賴於JVM開放的擴展點,使其可以在編譯的時候修改抽象語法樹,從而影響最終生成的字節碼

圖片來源地址:http://notatube.blogspot.com/2010/12/project-lombok-creating-custom.html

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

我們可以翻一下Lombok的源碼,Lombok對所有的註解都有兩套實現,javac和eclipse,由於我們的運行環境是Idea所以我們選擇javac的實現,javac版本的實現在lombok.javac.handlers.HandleBuilder#handle這個方法中

JavacNode parent = annotationNode.up();if (parent.get() instanceof JCClassDecl) { job.parentType = parent; JCClassDecl td = (JCClassDecl) parent.get(); ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>(); 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註解,站在抽象語法樹的角度,調用up方法得到的就是被註解修飾的類,也就是需要生成builder方法的類。

通過查看原始碼,@Builder註解是可以修飾類,構造函數和方法的,為了簡單起見,上面的代碼只截取了@Builder修飾類這一種情況,這段代碼關鍵的地方就在於調用HandleConstructor.findAllFields方法獲得類中所有的成員變量:

public static List<JavacNode> findAllFields(JavacNode typeNode, boolean evenFinalInitialized) { ListBuffer<JavacNode> fields = new ListBuffer<JavacNode>(); // 從抽象語法樹出發,遍歷類的所有的成員變量 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();}這段代碼比較簡單,就是對類中的成員變量做了過濾,比如說,靜態變量就不能被@Builder方法構造。有一個有意思的點,儘管$可以合法的出現在java的變量命名中,但是Lombok對這種變量做了過濾,因此變量名以$開始的也不能被@Builder構造,經過我們的驗證確實是這樣的。

如果我們用JDT AstView看一下ItemDTO的抽象語法樹結構,發現Java的抽象語法樹設計的確是每個類只包含顯式聲明的變量而不包括父類的成員變量(該插件支持點擊語法樹節點可以和源文件聯動,且數量只有4個和ItemDTO聲明的成員變量數量一致)

因為findAllFields方法是從當前類的抽象語法樹出發去找所有的成員變量,所以就只能找到當前類的成員變量,而訪問不到父類的成員變量

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

// 巴拉巴拉省略JCClassDecl td = (JCClassDecl) parent.get();// 獲取繼承的父類的抽象語法樹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<A>". 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);}// 巴拉巴拉省略可以看到,這裡拿到了繼承的父類的抽象語法樹,並在後面的邏輯中進行了處理,這裡不再贅述

相關焦點

  • Lombok使用指南
    >在 build.gradle 文件中添加 Lombok 依賴:dependencies { compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10'}Ant
  • 一份不可多得的 Lombok 學習指南
    一、Lombok 簡介  Lombok 是一款 Java 開發插件,使得 Java 開發者可以通過其定義的一些註解來消除業務工程中冗長和繁瑣的代碼,尤其對於簡單的 Java 模型對象(POJO)。
  • Lombok使用說明,到底該不該用
    Maven坐標代碼說明下面這段代碼我用Lombok的Data和Builder註解並通過builder來創建一個對象,代碼看起來非常簡潔。(2)在彈出的窗口中輸入lombok查詢,選中Lombok後,點擊Install開始安裝,安裝成功後重啟IDEA。Lombok帶來的問題任何事物都有兩面性,不可能那麼完美,Lombok也不例外。
  • 小辣椒神器Lombok
    咱們Java程式設計師在開發的時候,有大量簡單的POJO類,但是這類寫出來大量的getter,setter會導致代碼太過冗長,我們總是想,是否有更優雅的方式來解決這個問題,其實一直以來,都有一款神器可以幫助我們解決這個問題,就是馬上要給大家介紹的Lombok(又稱小辣椒)!
  • Intellij IDEA 安裝lombok及使用方法
    lombok是一個可以通過簡單的註解的形式來幫助我們簡化消除一些必須有但顯得很臃腫的 Java 代碼的工具,在我們項目開發中經常使用model,entity等類,絕大部分數據類類中都需要get、set、toString等方法,一般我們需要手動的添加這些屬性, 但是如果我們受到業務的變更
  • 優雅的代碼,高效的開發,Lombok來搞定
    Lombok安裝使用 lombok 是需要安裝的,這是它的一個缺點,因為如果不安裝,你的開發IDE是無法解析Lombok相關註解,還有一個因為這個帶來的問題是,如果有別的開發者下載了你的代碼,如果沒有安裝Lombok,代碼會出現錯誤。下面來說一下一種安裝步驟。
  • 知道這個插件,能讓你的項目裡少寫1000行代碼
    簡而來說:Lombok能以簡單的註解形式來簡化java代碼,提高開發人員的開發效率。2.安裝好了lombok,我們還需要把lombok依賴到我們項目。><version>1.18.4</version><scope>provided</scope></dependency>下面我們來嘗試下,使用lombok註解後,我們的實體類@Data這個註解是
  • IntelliJ IDEA安裝和開啟lombok
    簡述lombok是開源的代碼生成庫,是一款非常實用的小工具,lombok可以使實體類減少getter、setter等方法的編寫,在更改實體類時只需要修改屬性即可,減少了很多重複代碼的編寫工作。原料IntelliJ IDEA2020.01.01lombok安裝
  • 迷茫了,我們到底該不該用lombok?
    為什麼建議使用lombok?User類的主體只用定義成員變量,其他的方法全都交給註解來完成。如果修改了成員變量名稱或者類型,怎麼辦呢?編譯時解析又分為:註解處理器(Annotation Processing Tool)和 JSR 269 插入式註解處理器(Pluggable Annotation Processing API)第一種處理器它最早是在 JDK 1.5 與註解(Annotation) 一起引入的,它是一個命令行工具,能夠提供構建時基於原始碼對程序結構的讀取功能,能夠通過運行註解處理器來生成新的中間文件
  • IntelliJ IDEA 2020.2.1 發布,Lombok 插件可能被官方支持
    我一直認為是lombok插件作者沒有及時跟進的問題,事實上我錯怪作者米歇爾·普魯什尼科夫了。這是為什麼呢?2. lombok 失效的原因當我嘗試向lombok作者尋求答案時被告知「it's jetbrain's fault and not my fault.」,他表示他也很冤枉,他早在兩周前就已經同IDEA官方進行了溝通並尋求問題的解決方案。
  • 擁有眾多迷妹的Lombok了解一下
    Lombok的使用非常簡單,下面我們一起來看下:1)引入相應的maven包:<dependency><groupId>org.projectlombok</groupId><artifactId>
  • Lombok 看這篇就夠了
    下面看看如何在 IDEA中如何安裝 Lombok:安裝打開 IDEA 的 Settings 面板,並選擇 Plugins 選項,然後點擊 「Browse repositories」在輸入框輸入」lombok」,得到搜索結果,點擊安裝,然後安裝提示重啟 IDEA,安裝成功;
  • 你不得不會的Lombok全面詳細講解,全網最詳細的教程
    例如在實體中經常見到一堆Getter和Setter方法,這些方法是必要的不可缺少的,但是這些代碼感覺卻像是「垃圾」,看起來重複而臃腫,看起來也不美觀,也不簡潔清爽,可以使用lombok,在類上直接使用@Getter @Setter 這兩個註解,那麼代碼在編譯的時候會自動幫你生成這個類下的所有欄位對應的Getter和Setter方法,實體中只有一些屬性,看起來實體類變得簡潔很多
  • Lombok,你的開發效率神器!
    ->Plugins,然後搜索 Lombok 安裝即可;安裝後提示重啟 IDE 即可;在需要使用的項目中加入 Lombok 編譯支持,pom 文件中加入以下依賴;<dependency><groupId>org.projectlombok
  • 3D建模師講解3Dsmax與motionbuilder的區別
    3dsmax和motionbuilder都是三維動畫軟體,那麼3dsmax和motionbuilder這兩個軟體有什麼區別嗎?下面是小編整理的關於3dsmax和motionbuilder區別的講解。3dsmax和motionbuilder都是三維動畫軟體,那麼3dsmax和motionbuilder這兩個軟體有什麼區別嗎?下面是小編整理的關於3dsmax和motionbuilder區別的講解,希望對你們有幫助!什麼是3dsmax?
  • 雨刮器不好用怎麼辦?老司機:可能是這個原因,試試這個辦法!
    雨刮器不好用怎麼辦?老司機:可能是這個原因,試試這個辦法!雨刷器是一個橡膠的製品,經過長時間的暴曬或者是長時間的使用就會出現老化或者是開裂的現象,如果車主發現雨刷器不好用而且還有刺耳的聲音,雨刮器掛不乾淨?原因可能是這個,一個小妙招輕鬆解決!大家可以先檢查一下它的裡面是否有異物,如果有就會造成這種現象發生,而且這種異物可能會造成玻璃的劃痕出現。
  • 隨手記:eclpise安裝windowBuilder
    為啥要說這個呢,因為有的時候,這個swing在開發某些實用的小工具的時候,確實是比較方便。比如,我們做一個簡單的圖書館系統,實用swing開發之後,然後可以打包成一個可運行的jar包,或者是打包成我們熟悉的exe文件,就可以在任何電腦上面直接運行這個程序,這樣的話,在一個沒有java環境的電腦下就也是可以直接運行的,確實還是很方便的哈。
  • 3Dsmax與motionbuilder有什麼區別?看完小編的講解你估就明白了
    3dsmax和motionbuilder都是三維動畫軟體,那麼3dsmax和motionbuilder這兩個軟體有什麼區別嗎?下面是小編整理的關於3dsmax和motionbuilder區別的講解。3dsmax和motionbuilder都是三維動畫軟體,那麼3dsmax和motionbuilder這兩個軟體有什麼區別嗎?下面是小編整理的關於3dsmax和motionbuilder區別的講解,希望對你們有幫助!什麼是3dsmax?
  • 基於C++Builder API函數的歐姆龍PLC串行通信
    2 串行通信串行通信在工業系統控制的範疇中一直佔據著極其重要的地位,串行埠(rs-232)是計算機上的標準配置,常用於連接數據機來傳輸數據,在計算機的硬體設備管理器中可以看到,定義為com1、com2等。常用的串行通信方式有兩種,分別是rs-232和rs-485,本文以rs-232方式為例進行介紹。