<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在物件導向程式設計中,必不可少的需要在程式碼中定義物件模型,而在基於Java的業務平臺開發實踐中尤其如此。相信大家在平時開發中也深有感觸,本來是沒有多少程式碼開發量的,但是因為定義的業務模型物件比較多,而需要重複寫Getter/Setter、構造器方法、字串輸出的ToString方法、Equals/HashCode方法等。我們都知道Lombok能夠替大家完成這些繁瑣的操作,但是其背後的原理很少有人會關注或者說得清,本文會帶著大家瞭解這一開發神器內部的執行機制與原理!
Lombok是一款Java開發外掛,使得Java開發者可以通過其定義的一系列註解來消除業務工程中冗長和繁瑣的程式碼,尤其對於簡單的Java模型物件(POJO)。在開發環境中使用Lombok外掛後,Java開發人員可以節省出重複構建,諸如HashCode和Equals這樣的方法以及各種業務物件模型的accessor和ToString等方法的大量時間。對於這些方法,它能夠在編譯原始碼期間自動幫我們生成這些方法,且並不會如反射那樣降低程式的效能。主要是這樣比較靈活,即使你在實體類中新增了屬性,也不用重新回過頭來維護該實體的set和get方法等。
安裝外掛,在編譯類路徑中加入lombok.jar包(具體安裝方法可自己百度);
在需要簡化的類或方法上,加上要使用的註解;
使用支援lombok的編譯工具編譯原始碼(關於支援lombok的編譯工具,見4.支援lombok的編譯工具);
編譯得到的位元組碼檔案中自動生成Lombok註解對應的方法或程式碼;
接下來,我們進行lombok的原理分析,以Oracle的javac編譯工具為例。自Java 6起,javac開始支援JSR 269 Pluggable Annotation Processing API規範,只要程式實現了該API,就能在java原始碼編譯時呼叫定義的註解。舉例來說,現在有一個實現了"JSR 269 API"的程式A,那麼使用javac編譯原始碼的時候具體流程如下:
javac對原始碼進行分析,生成一棵抽象語法樹(AST);
執行過程中呼叫實現了"JSR 269 API"的A程式;
此時A程式就可以完成它自己的邏輯,包括修改第一步驟得到的抽象語法樹(AST);
javac使用修改後的抽象語法樹(AST)生成位元組碼檔案;
詳細的流程圖如下:
從上面的Lombok執行的流程圖中可以看出,在Javac 解析成AST抽象語法樹之後, Lombok 根據自己編寫的註解處理器,動態地修改 AST,增加新的節點(即Lombok自定義註解所需要生成的程式碼),最終通過分析生成JVM可執行的位元組碼Class檔案。使用Annotation Processing自定義註解是在編譯階段進行修改,而jdk的反射技術是在執行時動態修改,兩者相比,反射雖然更加靈活一些但是帶來的效能損耗更加大。
Lombok本質上就是一個實現了JSR 269 API的程式,在使用javac的命令過程中,它生效的具體流程如下:
使用的是idea工具進行開發,使用的jdk版本為1.8,因為我們是自己手寫的idea提示會報錯,但是能正常執行,因為lombok是idea針對於他有外掛提示,我們的沒有,但是也不影響正常使用。
1.我們需要使用到jdk安裝路徑下lib包下的tools.jar,我們可以收到加入到專案依賴,也可以在maven中直接引入。我們直接使用idea新建一個普通的maven專案,然後設定如下,最後將這個專案打包一下,在別的專案中引入即可。
maven設定如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>lombok</groupId> <artifactId>com.compass.lombok</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>C:/Program Files/Java/jdk1.8.0_251/lib/tools.jar</systemPath> </dependency> <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.0-rc5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
還有一個是 com.google.auto.service 這個是使用SPI機制的一個依賴,關於spi可以自行百度瞭解,這裡就不再進行展開。
關鍵核心介面:AbstractProcessor,這個就是在編譯期處理註解的一個介面,然後我們可以通過實現這個介面通過修改位元組碼檔案,最終在位元組碼檔案中生成get和set方法。
首先我們定義一個DATA註解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Data { }
然後寫一個 DataAnnotationProcessor 繼承AbstractProcessor即可
import com.compass.lombok.annotation.Data; import com.google.auto.service.AutoService; import com.sun.source.tree.Tree; import com.sun.tools.javac.api.JavacTrees; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeTranslator; import com.sun.tools.javac.util.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import java.util.Set; /** * @author compass * @date 2022-10-13 * @since 1.0 **/ @AutoService(Processor.class) @SupportedAnnotationTypes("com.compass.lombok.annotation.Data") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class DataAnnotationProcessor extends AbstractProcessor { private JavacTrees javacTrees; private TreeMaker treeMaker; private Names names; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); javacTrees = JavacTrees.instance(context); treeMaker = TreeMaker.instance(context); names = Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Data.class); for (Element element : set) { javacTrees.getTree(element).accept(new TreeTranslator(){ @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { jcClassDecl.defs.stream() .filter(it->it.getKind().equals(Tree.Kind.VARIABLE)) .map(it->(JCTree.JCVariableDecl) it).forEach(it->{ jcClassDecl.defs = jcClassDecl.defs.prepend(genGetterMethod(it)); jcClassDecl.defs = jcClassDecl.defs.prepend(genSetterMethod(it)); }); super.visitClassDef(jcClassDecl); } }); } return true; } private JCTree.JCMethodDecl genGetterMethod(JCTree.JCVariableDecl jcVariableDecl){ JCTree.JCIdent _this = treeMaker.Ident(names.fromString("this")); Name name = jcVariableDecl.getName(); JCTree.JCFieldAccess select = treeMaker.Select(_this, name); JCTree.JCReturn returnStatement = treeMaker.Return(select); ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append(returnStatement); JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC); Name getMethodName = getGetMethodName(jcVariableDecl.getName()); JCTree.JCExpression returnMethodType = jcVariableDecl.vartype; JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); List<JCTree.JCTypeParameter> methodGenericParamList = List.nil(); List<JCTree.JCVariableDecl> parameterList = List.nil(); List<JCTree.JCExpression> throwList = List.nil(); return treeMaker.MethodDef(modifiers, getMethodName, returnMethodType, methodGenericParamList, parameterList, throwList, body, null); } public JCTree.JCMethodDecl genSetterMethod(JCTree.JCVariableDecl jcVariableDecl){ JCTree.JCIdent _this = treeMaker.Ident(names.fromString("this")); Name name = jcVariableDecl.getName(); JCTree.JCFieldAccess select = treeMaker.Select(_this, name); JCTree.JCAssign statementAssign = treeMaker.Assign(select, treeMaker.Ident(jcVariableDecl.getName())); JCTree.JCExpressionStatement statement = treeMaker.Exec(statementAssign); ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append(statement); JCTree.JCVariableDecl params = treeMaker.VarDef( treeMaker.Modifiers(Flags.PARAMETER, List.nil()), jcVariableDecl.name, jcVariableDecl.vartype, null ); JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC); Name setMethodName = getSetMethodName(jcVariableDecl.getName()); JCTree.JCExpression returnMethodType = treeMaker.Type(new Type.JCVoidType()); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); List<JCTree.JCTypeParameter> methodGenericParamList = List.nil(); List<JCTree.JCVariableDecl> parameterList = List.of(params); List<JCTree.JCExpression> throwList = List.nil(); return treeMaker.MethodDef(modifiers, setMethodName, returnMethodType, methodGenericParamList, parameterList, throwList, body, null); } private Name getGetMethodName(Name name){ String filedName = name.toString(); return names.fromString("get"+filedName.substring(0,1).toUpperCase()+filedName.substring(1)); } private Name getSetMethodName(Name name){ String filedName = name.toString(); return names.fromString("set"+filedName.substring(0,1).toUpperCase()+filedName.substring(1)); } }
其實到這裡就編寫完畢了,這裡去動態修改位元組碼,然後生成了get和set方法,至於其他的方法那就後面再說,此案例參照於《深入jvm位元組碼》進行編寫。
最後在maven專案中打包
在別的專案直接使用即可,直接在別的專案的實體類上加上@Data註解即可生成get和set方法,但是沒有方法提升,但是能正常執行,這裡是idea的一個程式碼提示的問題,因為我們這個沒有對應的idea外掛,所以idea會提示報錯,但是能正常執行。
到此這篇關於利用Java手寫一個簡易的lombok的範例程式碼的文章就介紹到這了,更多相關Java手寫lombok內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45