利用Java手寫一個簡易的lombok的示例代碼
1.概述
在面向?qū)ο缶幊讨?,必不可少的需要在代碼中定義對象模型,而在基于Java的業(yè)務(wù)平臺開發(fā)實踐中尤其如此。相信大家在平時開發(fā)中也深有感觸,本來是沒有多少代碼開發(fā)量的,但是因為定義的業(yè)務(wù)模型對象比較多,而需要重復(fù)寫Getter/Setter、構(gòu)造器方法、字符串輸出的ToString方法、Equals/HashCode方法等。我們都知道Lombok能夠替大家完成這些繁瑣的操作,但是其背后的原理很少有人會關(guān)注或者說得清,本文會帶著大家了解這一開發(fā)神器內(nèi)部的運行機制與原理!
Lombok是一款Java開發(fā)插件,使得Java開發(fā)者可以通過其定義的一系列注解來消除業(yè)務(wù)工程中冗長和繁瑣的代碼,尤其對于簡單的Java模型對象(POJO)。在開發(fā)環(huán)境中使用Lombok插件后,Java開發(fā)人員可以節(jié)省出重復(fù)構(gòu)建,諸如HashCode和Equals這樣的方法以及各種業(yè)務(wù)對象模型的accessor和ToString等方法的大量時間。對于這些方法,它能夠在編譯源代碼期間自動幫我們生成這些方法,且并不會如反射那樣降低程序的性能。主要是這樣比較靈活,即使你在實體類中新增了屬性,也不用重新回過頭來維護該實體的set和get方法等。
2.lombok使用方法
安裝插件,在編譯類路徑中加入lombok.jar包(具體安裝方法可自己百度);
在需要簡化的類或方法上,加上要使用的注解;
使用支持lombok的編譯工具編譯源代碼(關(guān)于支持lombok的編譯工具,見4.支持lombok的編譯工具);
編譯得到的字節(jié)碼文件中自動生成Lombok注解對應(yīng)的方法或代碼;
3.lombok原理解析
接下來,我們進行l(wèi)ombok的原理分析,以O(shè)racle的javac編譯工具為例。自Java 6起,javac開始支持JSR 269 Pluggable Annotation Processing API規(guī)范,只要程序?qū)崿F(xiàn)了該API,就能在java源碼編譯時調(diào)用定義的注解。舉例來說,現(xiàn)在有一個實現(xiàn)了"JSR 269 API"的程序A,那么使用javac編譯源碼的時候具體流程如下:
javac對源代碼進行分析,生成一棵抽象語法樹(AST);
運行過程中調(diào)用實現(xiàn)了"JSR 269 API"的A程序;
此時A程序就可以完成它自己的邏輯,包括修改第一步驟得到的抽象語法樹(AST);
javac使用修改后的抽象語法樹(AST)生成字節(jié)碼文件;
詳細(xì)的流程圖如下:
從上面的Lombok執(zhí)行的流程圖中可以看出,在Javac 解析成AST抽象語法樹之后, Lombok 根據(jù)自己編寫的注解處理器,動態(tài)地修改 AST,增加新的節(jié)點(即Lombok自定義注解所需要生成的代碼),最終通過分析生成JVM可執(zhí)行的字節(jié)碼Class文件。使用Annotation Processing自定義注解是在編譯階段進行修改,而jdk的反射技術(shù)是在運行時動態(tài)修改,兩者相比,反射雖然更加靈活一些但是帶來的性能損耗更加大。
Lombok本質(zhì)上就是一個實現(xiàn)了JSR 269 API的程序,在使用javac的命令過程中,它生效的具體流程如下:
- javac對源代碼進行分析,生成一棵抽象語法樹(AST);
- 運行過程中調(diào)用實現(xiàn)了JSR 269 API的lombok程序;
- 編譯機會調(diào)用lombok程序?qū)Φ谝徊降玫降腁ST進行處理,找到其注解所在類對應(yīng)的語法樹(AST),然后修改該語法樹,增加注解對應(yīng)的方法或代碼片段到定義的相應(yīng)樹節(jié)點;
- javac使用修改后的抽象語法樹生成最終的java字節(jié)碼文件;
4.手寫簡易lombok
使用的是idea工具進行開發(fā),使用的jdk版本為1.8,因為我們是自己手寫的idea提示會報錯,但是能正常運行,因為lombok是idea針對于他有插件提示,我們的沒有,但是也不影響正常使用。
1.我們需要使用到j(luò)dk安裝路徑下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機制的一個依賴,關(guān)于spi可以自行百度了解,這里就不再進行展開。
關(guān)鍵核心接口:AbstractProcessor,這個就是在編譯期處理注解的一個接口,然后我們可以通過實現(xiàn)這個接口通過修改字節(jié)碼文件,最終在字節(jié)碼文件中生成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)); } }
其實到這里就編寫完畢了,這里去動態(tài)修改字節(jié)碼,然后生成了get和set方法,至于其他的方法那就后面再說,此案例參照于《深入jvm字節(jié)碼》進行編寫。
最后在maven項目中打包
在別的項目直接使用即可,直接在別的項目的實體類上加上@Data注解即可生成get和set方法,但是沒有方法提升,但是能正常運行,這里是idea的一個代碼提示的問題,因為我們這個沒有對應(yīng)的idea插件,所以idea會提示報錯,但是能正常運行。
到此這篇關(guān)于利用Java手寫一個簡易的lombok的示例代碼的文章就介紹到這了,更多相關(guān)Java手寫lombok內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java(SpringBoot)項目打包(構(gòu)建)成Docker鏡像的幾種常見方式
在對Spring Boot應(yīng)用程序進行Docker化時,為應(yīng)用程序選擇正確的基礎(chǔ)鏡像非常重要,下面這篇文章主要給大家介紹了關(guān)于Java(SpringBoot)項目打包(構(gòu)建)成Docker鏡像的幾種常見方式,需要的朋友可以參考下2023-12-12為什么說要慎用SpringBoot @ComponentScan
本文主要介紹了為什么說要慎用SpringBoot @ComponentScan,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07基于Java反射的map自動裝配JavaBean工具類設(shè)計示例代碼
這篇文章主要給大家介紹了關(guān)于基于Java反射的map自動裝配JavaBean工具類設(shè)計的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2018-10-10SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能
這篇文章主要介紹了SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能的實現(xiàn)代碼,感興趣的小伙伴們可以參考一下2016-06-06Springboot靜態(tài)資源訪問實現(xiàn)代碼解析
這篇文章主要介紹了Springboot靜態(tài)資源訪問實現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06Java RocketMQ 路由注冊與刪除的實現(xiàn)
這篇文章主要介紹了Java RocketMQ 路由注冊與刪除的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11