Lombok的@Builder不好用,試試@SuperBuilder吧!
@Builer, 它可以幫我們快速實(shí)現(xiàn)一個(gè)builder模式。以常見(jiàn)的商品模型為例:public 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
public 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)造父類中的成員變量

給BaseDTO上加上Builder注解也不會(huì)有任何效果。事實(shí)上,Builder注解只管承接注解的這個(gè)類,而不會(huì)管他的父類或者子類。如果真的是這樣的話,遇到有繼承的類,只好又打回原形,寫一堆的setter方法了。
試試SuperBuilder吧
這個(gè)問(wèn)題在lombokv1.18.2版本之前其實(shí)很難辦,但是在這個(gè)版本官方引入了一個(gè)新的注解@SuperBuilder,無(wú)法build父類的問(wèn)題迎刃而解
The
@SuperBuilderannotation produces complex builder APIs for your classes. In contrast to@Builder,@SuperBuilderalso works with fields from superclasses. However, it only works for types. Most importantly, it requires that all superclasses also have the@SuperBuilderannotation.
按照官方文檔的說(shuō)法,為了能夠使用build方法,只需要在子類和父類上都加@SuperBuilder注解,我們?cè)囈幌?/span>

果然現(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),javac和eclipse,由于我們的運(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();ListBufferallFields = 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 ListfindAllFields(JavacNode typeNode, boolean evenFinalInitialized) {ListBufferfields = 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 fieldsboolean 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ù)量一致)

因?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)行了處理,這里不再贅述

●Arthas原理系列(一):利用JVM的attach機(jī)制實(shí)現(xiàn)一個(gè)極簡(jiǎn)的watch命令
●Arthas原理系列(二):總體架構(gòu)和項(xiàng)目入口
●Arthas原理系列(三):服務(wù)端啟動(dòng)流程

掃描二維碼關(guān)注
我領(lǐng)取面試資料
