自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用了那么久的 Lombok,你知道它的原理么?

開(kāi)發(fā) 新聞
我們學(xué)會(huì)了如何自己寫(xiě)一個(gè)屬于自己的簡(jiǎn)易Lombok的插件。

序言

在寫(xiě)Java代碼的時(shí)候,最煩寫(xiě)setter/getter方法,自從有了Lombok插件不用再寫(xiě)那些方法之后,感覺(jué)再也回不去了,那你們是否好奇過(guò)Lombok是怎么把setter/getter方法給你加上去的呢?有的同學(xué)說(shuō)我們Java引入Lombok之后會(huì)污染依賴包,那我們可不可以自己寫(xiě)一個(gè)工具來(lái)代替Lombok呢?

知識(shí)點(diǎn)

  • Java編譯過(guò)程
  • 了解Lombok原理
  • 了解插入式注解處理器

分析

序言提到的問(wèn)題其實(shí)都是同一個(gè)問(wèn)題,就是如何去獲取和修改Java源代碼?

要回答這個(gè)問(wèn)題,我們需要回答這幾個(gè)問(wèn)題:

  1. Java編譯器是如何解析Java源代碼的?
  2. 編譯器編譯源代碼都有哪些步驟?
  3. 我們?cè)诰幾g器工作的時(shí)候,怎么才能去增加內(nèi)容或者是進(jìn)行代碼分析?

希望大家看完本文能夠自己寫(xiě)一個(gè)簡(jiǎn)易的Lombok工具。

回答

如何解析源代碼

其實(shí)從我們的代碼到被編譯,中間隔了一個(gè)數(shù)據(jù)結(jié)構(gòu),叫做AST(抽象樹(shù))。具體的形式,可以查看下面的圖片。右邊的便是AST的數(shù)據(jù)結(jié)構(gòu)了。

代碼編譯都有哪些步驟

整個(gè)編譯過(guò)程大致如下:

圖片來(lái)自 openjdk

1、初始化插入注解處理器

2、解析與填充符號(hào)表過(guò)程

a.詞法分析、語(yǔ)法分析。將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記集合,構(gòu)造出抽象語(yǔ)法樹(shù)。

b.填充符號(hào)表。產(chǎn)生符號(hào)地址和符號(hào)信息。

3、插入式注解處理器的注解處理過(guò)程:插入式注解處理器的執(zhí)行階段。后面我會(huì)給大家?guī)?lái)兩個(gè)此方面的實(shí)用實(shí)戰(zhàn)例子。

4、分析與字節(jié)碼生成過(guò)程

a.標(biāo)注檢查。對(duì)語(yǔ)法的靜態(tài)信息檢查。

b.數(shù)據(jù)流及控制流分析。對(duì)程序動(dòng)態(tài)運(yùn)行過(guò)程進(jìn)行檢查。

c.解語(yǔ)法糖。將簡(jiǎn)化代碼編寫(xiě)的語(yǔ)法糖還原為原有的形式。

d.字節(jié)碼生成。將前面各個(gè)步驟所生成的信息轉(zhuǎn)化成為字節(jié)碼。

?我們知道了上面的理論之后,接下來(lái)我們進(jìn)行實(shí)戰(zhàn)。帶著大家一起去修改AST(抽象樹(shù))。添加自己的代碼。

實(shí)戰(zhàn)

如何自己實(shí)現(xiàn)一個(gè)自動(dòng)添加Setter/Getter的工具?

首先,我們創(chuàng)建一個(gè)自己的注解。

@Retention(RetentionPolicy.SOURCE) // 注解只在源碼中保留
@Target(ElementType.TYPE) // 用于修飾類
public @interface MySetterGetter {
}

創(chuàng)建一個(gè)需要生成setter/getter方法的實(shí)體類

@MySetterGetter  // 打上我們的注解
public class Test {
private String wzj;
}

接下來(lái)就來(lái)看一看如何來(lái)生成我們想要的字符串。

整體代碼如下:

@SupportedAnnotationTypes("com.study.practice.nameChecker.MySetterGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MySetterGetterProcessor extends AbstractProcessor {
// 主要是輸出信息
private Messager messager;
private JavacTrees javacTrees;

private TreeMaker treeMaker;
private Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv){
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.javacTrees = 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)注的所有的類
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MySetterGetter.class);
elementsAnnotatedWith.forEach(element -> {
// 得到類的抽象樹(shù)結(jié)構(gòu)
JCTree tree = javacTrees.getTree(element);
// 遍歷類,對(duì)類進(jìn)行修改
tree.accept(new TreeTranslator(){
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl){
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// 在抽象樹(shù)中找出所有的變量
for(JCTree jcTree: jcClassDecl.defs){
if (jcTree.getKind().equals(Tree.Kind.VARIABLE)){
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl)jcTree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}

// 對(duì)于變量進(jìn)行生成方法的操作
for (JCTree.JCVariableDecl jcVariableDecl : jcVariableDeclList) {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));

jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
}


// 生成返回對(duì)象
JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());

return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewSetterMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null);
}
/**
* 生成 getter 方法
* @param jcVariableDecl
* @return
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl){
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
// 生成表達(dá)式
JCTree.JCReturn aReturn = treeMaker.Return(treeMaker.Ident(jcVariableDecl.getName()));
statements.append(aReturn);
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// 無(wú)入?yún)?/span>
// 生成返回對(duì)象
JCTree.JCExpression returnType = treeMaker.Type(jcVariableDecl.getType().type);
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewGetterMethodName(jcVariableDecl.getName()), returnType, List.nil(), List.nil(), List.nil(), block, null);
}
/**
* 拼裝Setter方法名稱字符串
* @param name
* @return
private Name getNewSetterMethodName(Name name){
String s = name.toString();
return names.fromString("set" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));
}
/**
* 拼裝 Getter 方法名稱的字符串
* @param name
* @return
private Name getNewGetterMethodName(Name name){
String s = name.toString();
return names.fromString("get" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));
}
/**
* 生成表達(dá)式
* @param lhs
* @param rhs
* @return
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs){
return treeMaker.Exec(
treeMaker.Assign(lhs, rhs)
);
}
}

代碼有點(diǎn)多,我們逐一拆解說(shuō)明:

下面這是整個(gè)代碼結(jié)構(gòu)的腦圖,后面的講解會(huì)基于這個(gè)順序。

a. 注解

@SupportedAnnotationTypes 表示我們需要監(jiān)聽(tīng)的注解,比如我們之前定義的 @MySetterGetter。

@SupportedSourceVersion 表示我們想要對(duì)什么版本的Java源代碼進(jìn)行處理。?

b. 父類

AbstractProcessor是本次的核心類,編譯器在編譯的時(shí)候會(huì)掃描此類的子類。其中有一個(gè)子類必須實(shí)現(xiàn)的核心方法 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv),此方法如果是返回為true就說(shuō)明編譯的那個(gè)類抽象樹(shù)的結(jié)構(gòu)又變化,需要重新進(jìn)行詞法分析和語(yǔ)法分析(可以查看上面提到的那個(gè)編譯流程圖)。如果返回的是false就說(shuō)明沒(méi)有變化。

c. process方法

主要的操作邏輯是:

1、拿到所有被我們MySetterGetter標(biāo)注的類。

2、遍歷所有的類,生成類的抽象樹(shù)結(jié)構(gòu)。

3、對(duì)類進(jìn)行操作:

a.找到類中所有的變量。

b.對(duì)變量進(jìn)行生成Set和Get方法。

4、返回 true,說(shuō)明類結(jié)構(gòu)變了,需要重新解析。如果是false說(shuō)明沒(méi)有變,不用重新解析。?

d. 操作JCTree樹(shù)

主要是在操作抽象樹(shù),可以查看文末附件中的文章進(jìn)行學(xué)習(xí)。

?e. 方法名稱拼接

這一塊兒和字符串拼接沒(méi)啥區(qū)別,用過(guò)反射的同學(xué)應(yīng)該也都清楚這個(gè)操作了。?

到此為止,我們就已經(jīng)介紹完了Lombok的原理。怎么樣是不是很簡(jiǎn)單。接下來(lái),就讓我們把它運(yùn)行起來(lái),投入到實(shí)戰(zhàn)之中。?

f. 運(yùn)行

最后來(lái)看一下如何正確的運(yùn)行這個(gè)我們寫(xiě)的工具。

1.環(huán)境

我的系統(tǒng)環(huán)境是 macOs Monterey;

java版本是

openjdk version "1.8.0_302"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (Temurin)(build 25.302-b08, mixed mode)

2.編譯processor

?在你存放 MySetterGetter 和 MySetterGetterProcessor 兩個(gè)類的目錄下進(jìn)行編譯。

javac -cp $JAVA_HOME/lib/tools.jar MySetterGetter.java MySetterGetterProcessor.java

執(zhí)行成功后會(huì)出現(xiàn)這三個(gè)class文件。

3.聲明插入式注解處理器

  • 在你的工程的resources下面創(chuàng)建一個(gè)包,名稱為:META-INFO.services
  • 然后創(chuàng)建一個(gè)文件,名稱為:javax.annotation.processing.Processor
  • 將你的注解處理器的地址填入,我的配置是這樣的:

com.study.practice.nameChecker.MySetterGetterProcessor

4.用我們的工具去編譯目標(biāo)類

比如我們本次是要編譯那個(gè)test.java。

它的內(nèi)容再回顧一下:

@MySetterGetter  // 打上我們的注解
public class Test {
private String wzj;
}

然后我們就去編譯它(注意類前面的路徑。這個(gè)你們得換成自己的工程目錄。)

javac -processor com.study.practice.nameChecker.MySetterGetterProcessor com/study/practice/nameChecker/Test.java

執(zhí)行之后如果沒(méi)有修改我的代碼的話會(huì)打印這幾個(gè)字符串:

process 1
process 2
: wzj has been processed
process 1

最后會(huì)生成Test.class文件。

5.成果

最后的class文件解析出來(lái)就是這個(gè)樣子的。如下圖所示:

看到Setter/Getter方法就說(shuō)明我們已經(jīng)大功告成了!是不是很簡(jiǎn)單。

到此為止,我們就學(xué)會(huì)了如何自己寫(xiě)一個(gè)屬于自己的簡(jiǎn)易Lombok的插件了。

附件

treemarker 的介紹:

http://www.docjar.com/docs/api/com/sun/tools/javac/tree/TreeMaker.html

責(zé)任編輯:張燕妮 來(lái)源: 阿里云云棲號(hào)
相關(guān)推薦

2022-08-11 17:14:37

Java

2022-06-27 07:32:00

JavaArrayList語(yǔ)法糖

2018-05-20 11:01:47

Siri語(yǔ)音助手手機(jī)

2021-11-08 10:00:19

require前端模塊

2021-07-21 10:10:14

require前端代碼

2019-12-18 15:11:42

數(shù)組集合數(shù)據(jù)

2019-11-27 10:54:43

Tomcat連接數(shù)線程池

2024-08-02 16:31:12

2021-02-18 16:06:43

JavaStream代碼

2018-08-20 20:46:07

2018-01-31 10:24:45

熱插拔原理服務(wù)器

2017-12-19 11:54:51

微信朋友圈同步

2020-12-07 11:05:21

HttpClient代碼Java

2022-02-08 13:39:35

LinuxUNIX系統(tǒng)

2025-03-25 10:49:13

2020-06-03 08:15:50

IO軟件

2022-01-25 12:41:31

ChromeResponse接口

2023-01-13 16:53:17

Annotation底層元注解

2020-02-15 15:33:55

Python如何運(yùn)作

2010-01-06 15:36:30

Linux操作系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)