Lombok實(shí)現(xiàn)方式JSR-269
前言
簡(jiǎn)介
Lombok是一款好用順手的工具,就像Google Guava一樣,在此予以強(qiáng)烈推薦,每一個(gè)Java工程師都應(yīng)該使用它。Lombok是一種Java™實(shí)用工具,可用來(lái)幫助開(kāi)發(fā)人員消除Java的冗長(zhǎng)代碼,尤其是對(duì)于簡(jiǎn)單的Java對(duì)象(POJO)。它通過(guò)注釋實(shí)現(xiàn)這一目的。通過(guò)在開(kāi)發(fā)環(huán)境中實(shí)現(xiàn)Lombok,開(kāi)發(fā)人員可以節(jié)省構(gòu)建諸如hashCode()和equals()這樣的方法以及以往用來(lái)分類(lèi)各種accessor和mutator的大量時(shí)間。
Lombok的實(shí)現(xiàn)方式是什么呢?
新建一個(gè)測(cè)試類(lèi)使用Lombok的Getter和Setter注解,通過(guò)IDEA進(jìn)行編譯
import lombok.Getter; import lombok.Setter; @Getter @Setter public class UserInfo { private String userId; private String userName; }
打開(kāi)編譯后生成的UserInfo.class文件
發(fā)現(xiàn)已經(jīng)生成了get、set方法,由此可以推斷出Lombok是在編譯期為代碼進(jìn)行了增強(qiáng),那么在編譯期進(jìn)行增強(qiáng)是如何實(shí)現(xiàn)的?
編譯階段
在JDK6提出并通過(guò)了JSR-269提案,提案通過(guò)了一組被稱(chēng)為“插入式注解處理器”的標(biāo)準(zhǔn)API,可以提前至編譯期對(duì)代碼中的特定注解進(jìn)行處理, 從而影響到編譯器的工作過(guò)程。
對(duì)于底層的一些實(shí)現(xiàn),普遍會(huì)認(rèn)為實(shí)現(xiàn)是像虛擬機(jī)一樣使用C++實(shí)現(xiàn),對(duì)于Java程序員來(lái)說(shuō)并不是特別友好。但是Javac編譯器是使用Java實(shí)現(xiàn)的更容易上手。
Javac的編譯過(guò)程大致分為幾步
- 準(zhǔn)備過(guò)程:初始化插入式注解處理器
- 解析與填充符號(hào)表過(guò)程 詞法、語(yǔ)法分析構(gòu)建抽象語(yǔ)法樹(shù)(AST)
- 插入式注解處理器的注解處理過(guò)程
- 分析與字節(jié)碼生成過(guò)程
- 語(yǔ)法樹(shù)變動(dòng)后會(huì)再次解析與填充符號(hào)表,語(yǔ)法樹(shù)沒(méi)有變動(dòng)時(shí)編譯器就不會(huì)再對(duì)源碼字符流操作,而是基于抽象語(yǔ)法樹(shù)
綜上所述想實(shí)現(xiàn)Lombok的效果只需要遵守JSR-269在編譯期對(duì)AST進(jìn)行操作即可實(shí)現(xiàn)
當(dāng)然不止有Lombok通過(guò)這種方式實(shí)現(xiàn),例如FindBug、MapStruct等也通過(guò)這種方式實(shí)現(xiàn)
實(shí)現(xiàn)
JCTree
JCTree是AST元素的基類(lèi),想實(shí)現(xiàn)效果只需要添加JCTree節(jié)點(diǎn)即可
JCTree是一個(gè)抽象類(lèi)部分實(shí)現(xiàn)
從類(lèi)名可以猜到是什么節(jié)點(diǎn)使用的這里挑幾個(gè)常用的解釋
JCStatement 聲明語(yǔ)法樹(shù)節(jié)點(diǎn)
JCBlock 語(yǔ)法塊
JCReturn:return語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)
JCClassDecl:類(lèi)定義語(yǔ)法樹(shù)節(jié)點(diǎn)
JCVariableDecl:字段/變量定義語(yǔ)法樹(shù)節(jié)點(diǎn)
JCMethodDecl:方法定義語(yǔ)法樹(shù)節(jié)點(diǎn)
JCModifiers:訪問(wèn)標(biāo)志語(yǔ)法樹(shù)節(jié)點(diǎn)
JCExpression:表達(dá)式語(yǔ)法樹(shù)節(jié)點(diǎn),常見(jiàn)的子類(lèi)如下
JCAssign:賦值語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)
JCIdent:標(biāo)識(shí)符語(yǔ)法樹(shù)節(jié)點(diǎn) 例如this
TreeMaker
主要用于生成語(yǔ)法樹(shù)節(jié)點(diǎn)
代碼
首先需要注解類(lèi),標(biāo)明作用的范圍和作用的時(shí)期,兩個(gè)類(lèi)分別對(duì)應(yīng)Lombok的Getter、Setter
@Target({ElementType.TYPE}) //加在類(lèi)上的注解 @Retention(RetentionPolicy.SOURCE) //作用于編譯期 public @interface Getter { } @Target({ElementType.TYPE}) //加在類(lèi)上的注解 @Retention(RetentionPolicy.SOURCE) //作用于編譯期 public @interface Setter { }
新建抽象注解處理器需要繼承AbstractProcessor,這里使用模板方法模式
public abstract class MyAbstractProcessor extends AbstractProcessor { //語(yǔ)法樹(shù) protected JavacTrees trees; //構(gòu)建語(yǔ)法樹(shù)節(jié)點(diǎn) protected TreeMaker treeMaker; //創(chuàng)建標(biāo)識(shí)符的對(duì)象 protected Names names; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.trees = JavacTrees.instance(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.treeMaker = TreeMaker.instance(context); this.names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //獲取注解標(biāo)識(shí)的類(lèi) Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation()); //拿到語(yǔ)法樹(shù) set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() { //拿到類(lèi)定義 @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil(); //拿到所有成員變量 for (JCTree tree : jcClassDecl.defs) { if (tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); } } jcVariableDeclList.forEach(jcVariableDecl -> { jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl)); }); super.visitClassDef(jcClassDecl); } })); return true; } /** * 創(chuàng)建方法 * @param jcVariableDecl * @return */ public abstract JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl); /** * 獲取何種注解 * @return */ public abstract Class<? extends Annotation> getAnnotation(); }
用于處理Setter注解 繼承MyAbstractProcessor
@SupportedAnnotationTypes("com.ingxx.processor.Setter") //注解處理器作用于哪個(gè)注解 也可以重寫(xiě)getSupportedAnnotationTypes @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以處理什么版本 也可以重寫(xiě)getSupportedSourceVersion public class SetterProcessor extends MyAbstractProcessor { @Override public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); //生成函數(shù)體 this.name = name; statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName())))); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); //生成方法 return treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), //訪問(wèn)標(biāo)志 getNewMethodName(jcVariableDecl.getName()), //名字 treeMaker.TypeIdent(TypeTag.VOID), //返回類(lèi)型 List.nil(), //泛型形參列表 List.of(getParameters(jcVariableDecl)), //參數(shù)列表 List.nil(), //異常列表 body, //方法體 null //默認(rèn)方法(可能是interface中的那個(gè)default) ); } @Override public Class<? extends Annotation> getAnnotation() { return Setter.class; } private Name getNewMethodName(Name name) { String fieldName = name.toString(); return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length())); } private JCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) { return treeMaker.VarDef( treeMaker.Modifiers(Flags.PARAMETER), //訪問(wèn)標(biāo)志 prototypeJCVariable.name, //名字 prototypeJCVariable.vartype, //類(lèi)型 null //初始化語(yǔ)句 ); } }
用于處理Getter注解 繼承MyAbstractProcessor
@SupportedAnnotationTypes("com.ingxx.processor.Getter") //注解處理器作用于哪個(gè)注解 也可以重寫(xiě)getSupportedAnnotationTypes @SupportedSourceVersion(SourceVersion.RELEASE_8) //可以處理什么版本 也可以重寫(xiě)getSupportedSourceVersion public class GetterProcessor extends MyAbstractProcessor { @Override public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) { ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); //生成函數(shù)體 return this.字段名 statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()))); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); //生成方法 return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null); } @Override public Class<? extends Annotation> getAnnotation() { return Getter.class; } private Name getNewMethodName(Name name) { String fieldName = name.toString(); return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length())); } }
編譯現(xiàn)有類(lèi)
javac -cp $JAVA_HOME/lib/tools.jar *.java -d .
新建一個(gè)測(cè)試類(lèi) IDEA中由于找不到get set方法會(huì)報(bào)錯(cuò),可以忽略
@Getter @Setter public class UserInfo { private String userId; private String userName; public static void main(String[] args) { UserInfo userInfo = new UserInfo(); userInfo.setUserId("001"); userInfo.setUserName("得物"); System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName()); } }
接著編譯
//多個(gè)處理器用逗號(hào)分隔 javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .
查看編譯后的文件發(fā)現(xiàn)已經(jīng)生成了get、set方法
以上就是從Lombok到JSR-269的詳細(xì)內(nèi)容,更多關(guān)于JS 反射機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 解決IDEA2020.2插件lombok報(bào)錯(cuò)問(wèn)題(親測(cè)有效)
- IDEA下因Lombok插件產(chǎn)生的Library source does not match the bytecode報(bào)錯(cuò)問(wèn)題及解決方法(親測(cè)可用)
- IDEA安裝lombok插件設(shè)置Enable Annotation Processing后編譯依然報(bào)錯(cuò)解決方法
- IDEA下lombok安裝及找不到get,set的問(wèn)題的解決方法
- 解決IDEA2020.1版本不兼容Lombok的問(wèn)題
- 詳解Idea 2019.2 安裝lombok插件失效問(wèn)題解決
相關(guān)文章
JavaScript中判斷為整數(shù)的多種方式及保留兩位小數(shù)的方法
這篇文章主要介紹了JavaScript中判斷為整數(shù)的多種方式,以及保留兩位小數(shù)的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09JavaScript 組件之旅(四):測(cè)試 JavaScript 組件
本期,我們要討論的話題是 JavaScript 的測(cè)試,以檢查組件的狀態(tài)和工作方式是否符合預(yù)期,還會(huì)介紹一個(gè)可以方便編寫(xiě)測(cè)試用例的測(cè)試方法。這里說(shuō)的測(cè)試當(dāng)然是使用自動(dòng)化的測(cè)試手段,這是軟件質(zhì)量保證(QA)的重要環(huán)節(jié)。2009-10-10js將鍵值對(duì)字符串轉(zhuǎn)為json字符串的方法
下面小編就為大家分享一篇js將鍵值對(duì)字符串轉(zhuǎn)為json字符串的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03JS實(shí)現(xiàn)掃雷項(xiàng)目總結(jié)
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)掃雷項(xiàng)目總結(jié),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05js實(shí)現(xiàn)html滑動(dòng)圖片拼圖驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)html滑動(dòng)圖片拼圖驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06js獲取input標(biāo)簽的輸入值實(shí)現(xiàn)代碼
input標(biāo)簽的輸入值通過(guò)js進(jìn)行獲取,部分標(biāo)簽和類(lèi)是封裝在框架內(nèi)的,其效果和html標(biāo)簽差不多,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08js實(shí)現(xiàn)圖片上傳即時(shí)顯示效果
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)圖片上傳即時(shí)顯示效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09詳解JavaScript前端如何有效處理本地存儲(chǔ)和緩存
前端本地存儲(chǔ)和緩存的處理是一種重要的技術(shù),它可以幫助改善應(yīng)用程序的性能和用戶體驗(yàn),下面是小編整理的一些處理前端本地存儲(chǔ)和緩存的常用方法,希望對(duì)大家有所幫助2023-11-11