最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

APT?注解處理器實(shí)現(xiàn)?Lombok?常用注解功能詳解

 更新時(shí)間:2022年09月20日 10:03:58   作者:政采云技術(shù)團(tuán)隊(duì)  
這篇文章主要為大家介紹了使用APT?注解處理器實(shí)現(xiàn)?Lombok?常用注解功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

1 背景

在開發(fā)中我們常常會(huì)用到類似 lombok 、mapstruct 或者 mybatisplus 的框架,只要加入幾個(gè)注解即可生成對(duì)應(yīng)的方法,既然被很多框架使用,了解其中的原理還是非常有必要的。

2 生成字節(jié)碼原理

2.1 APT(Annotation Processing Tool )注解處理器

基于 JSR 269(Pluggable Annotation Processing API)規(guī)范,提供插入式注解處理接口,Java 6 開始支持,它的主要功能是在 Java 編譯期對(duì)源碼進(jìn)行處理, 通過這些規(guī)范插件,可以讀取、修改、添加抽象語法樹中的任意元素。

如上圖 Javac 在編器期間,如果使用注解處理器對(duì)語法樹進(jìn)行了修改,編譯器將回到解析及填充符號(hào)表的過程重新處理,直至處理完成,再對(duì)語法樹進(jìn)行修改。

2.2 AbstractProcessor 注解處理器的使用

創(chuàng)建一個(gè)注解處理器分為如下幾步:

  • 創(chuàng)建注解類 : 比如 @Data
  • 創(chuàng)建 AbstractProcessor 的繼承類, APT 的核心類
  • 修改生成字節(jié)碼
  • SPI配置: 在 META-INF\services 創(chuàng)建名為 javax.annotation.processing.Processor 配置文件添加 SPI 實(shí)現(xiàn)

2.3 APT 、 AOP、 JavaAgent 優(yōu)缺點(diǎn)

在我們?nèi)粘i_發(fā)中,如果需要做一些埋點(diǎn),AOP 并非唯一選擇,APT 在有些場景下也可以使用的,支持靜態(tài)方法和私有方法,同時(shí)穩(wěn)定性也比較好,覆蓋的場景比較全。

2.4 lombok 原理

1 APT(Annotation Processing Tool )注解處理器 2 javac api處理AST(抽象語法樹)

大致原理如下圖所示:

如想具體分析 lombok 的實(shí)現(xiàn),可以從 ProcessorAnnotationProcessor 這兩個(gè)類的 process 方法入手,通過 lombok.javac.JavacAnnotationHandler 處理器找到對(duì)應(yīng)的注解實(shí)現(xiàn)。

3 自己實(shí)現(xiàn)Lombok

3.1 創(chuàng)建Data注解

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface Data {
}

該 Data 注解只能在編譯期的時(shí)候獲取到,在運(yùn)行期是無法獲取到的。

3.2 自定義注解處理器

通過實(shí)現(xiàn)Processor 接口可以自定義注解處理器,這里我們采用更簡單的方法通過繼承AbstractProcessor 類實(shí)現(xiàn)自定義注解處理器, 實(shí)現(xiàn)抽象方法 process 處理我們想要的功能。

3.2.1 APT簡單介紹

@SupportedAnnotationTypes({"com.nicky.lombok.annotation.Data"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DataProcessor extends AbstractProcessor {
   @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    }
}

@SupportedAnnotationTypes 注解表示哪些注解需要注解處理器處理,可以多個(gè)注解校驗(yàn) @SupportedSourceVersion 注解 用于指定jdk使用版本

如果不使用注解也可以在重寫父類方法

Set<String> getSupportedAnnotationTypes() 
SourceVersion getSupportedSourceVersion
...
  • init 方法

主要是用于初始化上下文等信息

  • process方法

具體處理注解的業(yè)務(wù)方法

3.2.2 具體實(shí)現(xiàn)

  • 1 重寫init方法
  /**
     * 抽象語法樹
     */
    private JavacTrees trees;
    /**
     * AST
     */
    private TreeMaker treeMaker;
    /**
     * 標(biāo)識(shí)符
     */
    private Names names;
    /**
     * 日志處理
     */
    private Messager messager;
    private Filer filer;
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment)processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        messager = processingEnvironment.getMessager();
        this.names = Names.instance(context);
        filer = processingEnvironment.getFiler();
    }

基本成員變量說明:

  • 1 JavacTrees 這個(gè)是當(dāng)前的java語法樹變量
  • 2 TreeMaker 這個(gè)是創(chuàng)建或修改方法的AST變量
  • 3 Names 這個(gè)是獲取變量用的
  • 4 Messager 這個(gè)是打印日志的變量
  • 5 Filer 做一些過濾使用的

注: 使用AST語法需要使用本地包 tools.jar 包

    <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>
  • 2 重寫process方法
 @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> annotation = roundEnv.getElementsAnnotatedWith(Data.class);
        annotation.stream().map(element -> trees.getTree(element)).forEach(tree -> tree.accept(new TreeTranslator() {
            @Override
            public void visitClassDef(JCClassDecl jcClass) {
                //過濾屬性
                Map<Name, JCVariableDecl> treeMap =
                    jcClass.defs.stream().filter(k -> k.getKind().equals(Tree.Kind.VARIABLE))
                        .map(tree -> (JCVariableDecl)tree)
                        .collect(Collectors.toMap(JCVariableDecl::getName, Function.identity()));
                //處理變量
                treeMap.forEach((k, jcVariable) -> {
                    messager.printMessage(Diagnostic.Kind.NOTE, String.format("fields:%s", k));
                    try {
                        //增加get方法
                        jcClass.defs = jcClass.defs.prepend(generateGetterMethod(jcVariable));
                        //增加set方法
                        jcClass.defs = jcClass.defs.prepend(generateSetterMethod(jcVariable));
                    } catch (Exception e) {
                        messager.printMessage(Diagnostic.Kind.ERROR, Throwables.getStackTraceAsString(e));
                    }
                });
                //增加toString方法
                jcClass.defs = jcClass.defs.prepend(generateToStringBuilderMethod());
                super.visitClassDef(jcClass);
            }
            @Override
            public void visitMethodDef(JCMethodDecl jcMethod) {
                //打印所有方法
                messager.printMessage(Diagnostic.Kind.NOTE, jcMethod.toString());
                //修改方法
                if ("getTest".equals(jcMethod.getName().toString())) {
                    result = treeMaker
                        .MethodDef(jcMethod.getModifiers(), getNameFromString("testMethod"), jcMethod.restype,
                            jcMethod.getTypeParameters(), jcMethod.getParameters(), jcMethod.getThrows(),
                            jcMethod.getBody(), jcMethod.defaultValue);
                }
                super.visitMethodDef(jcMethod);
            }
        }));
        return true;
    }

上面邏輯分別實(shí)現(xiàn)了getter方法 setter方法 toString方法

大致邏輯:

1 過濾包含Data的 Element 變量 2 根據(jù) Element 獲取AST語法樹 3 創(chuàng)建語法翻譯器重寫 visitClassDefvisitMethodDef 方法 4 過濾變量生成 get方法 set方法 和 toString方法

  • 3 get方法實(shí)現(xiàn)
  private JCMethodDecl generateGetterMethod(JCVariableDecl jcVariable) {
        //修改方法級(jí)別
        JCModifiers jcModifiers = treeMaker.Modifiers(Flags.PUBLIC);
        //添加方法名稱
        Name methodName = handleMethodSignature(jcVariable.getName(), "get");
        //添加方法內(nèi)容
        ListBuffer<JCStatement> jcStatements = new ListBuffer<>();
        jcStatements.append(
            treeMaker.Return(treeMaker.Select(treeMaker.Ident(getNameFromString("this")), jcVariable.getName())));
        JCBlock jcBlock = treeMaker.Block(0, jcStatements.toList());
        //添加返回值類型
        JCExpression returnType = jcVariable.vartype;
        //參數(shù)類型
        List<JCTypeParameter> typeParameters = List.nil();
        //參數(shù)變量
        List<JCVariableDecl> parameters = List.nil();
        //聲明異常
        List<JCExpression> throwsClauses = List.nil();
        //構(gòu)建方法
        return treeMaker
            .MethodDef(jcModifiers, methodName, returnType, typeParameters, parameters, throwsClauses, jcBlock, null);
    }
  • 4 set方法實(shí)現(xiàn)
    private JCMethodDecl generateSetterMethod(JCVariableDecl jcVariable) throws ReflectiveOperationException {
        //修改方法級(jí)別
        JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        //添加方法名稱
        Name variableName = jcVariable.getName();
        Name methodName = handleMethodSignature(variableName, "set");
        //設(shè)置方法體
        ListBuffer<JCStatement> jcStatements = new ListBuffer<>();
        jcStatements.append(treeMaker.Exec(treeMaker
            .Assign(treeMaker.Select(treeMaker.Ident(getNameFromString("this")), variableName),
                treeMaker.Ident(variableName))));
        //定義方法體
        JCBlock jcBlock = treeMaker.Block(0, jcStatements.toList());
        //添加返回值類型
        JCExpression returnType =
            treeMaker.Type((Type)(Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance()));
        List<JCTypeParameter> typeParameters = List.nil();
        //定義參數(shù)
        JCVariableDecl variableDecl = treeMaker
            .VarDef(treeMaker.Modifiers(Flags.PARAMETER, List.nil()), jcVariable.name, jcVariable.vartype, null);
        List<JCVariableDecl> parameters = List.of(variableDecl);
        //聲明異常
        List<JCExpression> throwsClauses = List.nil();
        return treeMaker
            .MethodDef(modifiers, methodName, returnType, typeParameters, parameters, throwsClauses, jcBlock, null);
    }
  • 5 toString方法實(shí)現(xiàn)
  private JCMethodDecl generateToStringBuilderMethod() {
        //修改方法級(jí)別
        JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        //添加方法名稱
        Name methodName = getNameFromString("toString");
        //設(shè)置調(diào)用方法函數(shù)類型和調(diào)用函數(shù)
        JCExpressionStatement statement = treeMaker.Exec(treeMaker.Apply(List.of(memberAccess("java.lang.Object")),
            memberAccess("com.nicky.lombok.adapter.AdapterFactory.builderStyleAdapter"),
            List.of(treeMaker.Ident(getNameFromString("this")))));
        ListBuffer<JCStatement> jcStatements = new ListBuffer<>();
        jcStatements.append(treeMaker.Return(statement.getExpression()));
        //設(shè)置方法體
        JCBlock jcBlock = treeMaker.Block(0, jcStatements.toList());
        //添加返回值類型
        JCExpression returnType = memberAccess("java.lang.String");
        //參數(shù)類型
        List<JCTypeParameter> typeParameters = List.nil();
        //參數(shù)變量
        List<JCVariableDecl> parameters = List.nil();
        //聲明異常
        List<JCExpression> throwsClauses = List.nil();
        return treeMaker
            .MethodDef(modifiers, methodName, returnType, typeParameters, parameters, throwsClauses, jcBlock, null);
    }
 private JCExpression memberAccess(String components) {
        String[] componentArray = components.split("\\.");
        JCExpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
        for (int i = 1; i < componentArray.length; i++) {
            expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
        }
        return expr;
    }
    private Name handleMethodSignature(Name name, String prefix) {
        return names.fromString(prefix + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, name.toString()));
    }
    private Name getNameFromString(String s) {
        return names.fromString(s);
    }

最后是通過 SPI 的方式加載注解處理器,spi 可以用 java 自帶的方式,具體用法可以參考我的文章:框架基礎(chǔ)之SPI機(jī)制 , 這里我們使用 google 封裝的 auto-service 框架來實(shí)現(xiàn)。

在pom文件中引入

        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc4</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.google.auto</groupId>
            <artifactId>auto-common</artifactId>
            <version>0.10</version>
            <optional>true</optional>
        </dependency>

然后在添加AutoService注解

@SupportedAnnotationTypes({"com.nicky.lombok.annotation.Data"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class DataProcessor extends AbstractProcessor {
}

最后就是 mvn clean install 打包到本地倉庫作為一個(gè)公共包

[INFO] Installing /Users/chenxing/Documents/sourcecode/id-generator-spring-boot-starter/lombok-enchance/target/java-feature.jar to /Users/chenxing/m2repository/com/nicky/lombok-enchance/1.0.4/lombok-enchance-1.0.4.jar
[INFO] Installing /Users/chenxing/Documents/sourcecode/id-generator-spring-boot-starter/lombok-enchance/pom.xml to /Users/chenxing/m2repository/com/nicky/lombok-enchance/1.0.4/lombok-enchance-1.0.4.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.372 s
[INFO] Finished at: 2022-09-03T10:44:27+08:00
[INFO] ------------------------------------------------------------------------
?  lombok-enchance git:(master) ? 

我們測(cè)試下,我們的注解處理器是否按所想的那樣,實(shí)現(xiàn)了相應(yīng)功能。

在項(xiàng)目中引入本地依賴 例如我的倉庫依賴坐標(biāo):

        <dependency>
            <groupId>com.nicky</groupId>
            <artifactId>lombok-enchance</artifactId>
            <version>1.0.4</version>
        </dependency>

給LombokTest 類添加 @Data 注解

@Data
public class LombokTest {
    private String name;
    private int age;
    public LombokTest(String name) {
        this.name = name;
    }
    public static void main(String[] args) {
        LombokTest lombokTest = new LombokTest("nicky");
        lombokTest.age = 18;
        System.out.println(lombokTest.toString());
    }
}

我們編譯上面的類,查看 class文件是否生成了getField() setField() toString() 方法

public class LombokTest {
    private java.lang.String name;
    private int age;
    public java.lang.String toString() { /* compiled code */ }
    public void setName(java.lang.String name) { /* compiled code */ }
    public java.lang.String getName() { /* compiled code */ }
    public void setAge(int age) { /* compiled code */ }
    public int getAge() { /* compiled code */ }
    public LombokTest(java.lang.String name) { /* compiled code */ }
    public static void main(java.lang.String[] args) { /* compiled code */ }
}

成功啦 ??

最后測(cè)試下main方法

打印結(jié)果如下:

{"name":"清水","age":18}

說明toString方法生效了。

當(dāng)然對(duì)于 get 和 set 方法 直接在IDE工具里還是無法調(diào)用的,需要編寫 IDE 的 Lombok 插件,這里就不去擴(kuò)展了。

Reference

以上就是APT 注解處理器實(shí)現(xiàn) Lombok 常用注解功能詳解的詳細(xì)內(nèi)容,更多關(guān)于APT 實(shí)現(xiàn)Lombok注解功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Mybatis示例之SelectKey的應(yīng)用

    Mybatis示例之SelectKey的應(yīng)用

    今天小編就為大家分享一篇關(guān)于Mybatis示例之SelectKey的應(yīng)用,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • 論java如何通過反射獲得方法真實(shí)參數(shù)名及擴(kuò)展研究

    論java如何通過反射獲得方法真實(shí)參數(shù)名及擴(kuò)展研究

    這篇文章主要為大家介紹了java如何通過反射獲得方法的真實(shí)參數(shù)名以及擴(kuò)展研究,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步早日升職加薪
    2022-01-01
  • Spring代理對(duì)象導(dǎo)致的獲取不到原生對(duì)象注解的解決

    Spring代理對(duì)象導(dǎo)致的獲取不到原生對(duì)象注解的解決

    本文主要介紹了Spring代理對(duì)象導(dǎo)致的獲取不到原生對(duì)象注解的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • MyBatis Generator的簡單使用方法示例

    MyBatis Generator的簡單使用方法示例

    這篇文章主要給大家介紹了關(guān)于MyBatis Generator的簡單使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • 解決從Map、JSONObject取不存在鍵值對(duì)時(shí)的異常情況

    解決從Map、JSONObject取不存在鍵值對(duì)時(shí)的異常情況

    這篇文章主要介紹了解決從Map、JSONObject取不存在鍵值對(duì)時(shí)的異常情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 一篇文章帶你學(xué)會(huì)Spring?MVC表單標(biāo)簽

    一篇文章帶你學(xué)會(huì)Spring?MVC表單標(biāo)簽

    Spring MVC表單標(biāo)簽是網(wǎng)頁的可配置和可重復(fù)使用的構(gòu)建塊,下面這篇文章主要給大家介紹了如何通過一篇文章學(xué)會(huì)Spring?MVC表單標(biāo)簽的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • Java實(shí)現(xiàn)快速將HTML表格轉(zhuǎn)換成Excel

    Java實(shí)現(xiàn)快速將HTML表格轉(zhuǎn)換成Excel

    這篇文章主要為大家詳細(xì)介紹一種使用Java的快速將Web中表格轉(zhuǎn)換成Excel的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-05-05
  • springboot后端存儲(chǔ)富文本內(nèi)容的思路與步驟(含圖片內(nèi)容)

    springboot后端存儲(chǔ)富文本內(nèi)容的思路與步驟(含圖片內(nèi)容)

    在所有的編輯器中,大概最受歡迎的就是富文本編輯器和MarkDown編輯器了,下面這篇文章主要給大家介紹了關(guān)于springboot后端存儲(chǔ)富文本內(nèi)容的思路與步驟的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • SpringBoot2整合Redis實(shí)現(xiàn)讀寫操作

    SpringBoot2整合Redis實(shí)現(xiàn)讀寫操作

    Redis,對(duì)于大家來說應(yīng)該不陌生,是經(jīng)常使用的開發(fā)技術(shù)之一。本文將結(jié)合實(shí)例代碼,介紹SpringBoot2整合Redis實(shí)現(xiàn)讀寫操作,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Spring MVC 文件上傳下載的實(shí)例

    Spring MVC 文件上傳下載的實(shí)例

    本篇文章主要介紹了Spring MVC 文件上傳下載的實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01

最新評(píng)論

汉川市| 屏东县| 兰溪市| 四子王旗| 聊城市| 永和县| 洛川县| 彰化县| 益阳市| 土默特左旗| 敦化市| 出国| 阿克陶县| 南宁市| 溧水县| 广州市| 策勒县| 合肥市| 镇康县| 青州市| 荆州市| 班玛县| 和林格尔县| 竹北市| 南澳县| 新巴尔虎右旗| 常州市| 屏山县| 凭祥市| 阿克苏市| 民权县| 汶川县| 历史| 扎鲁特旗| 登封市| 太保市| 宁远县| 三台县| 卫辉市| 南阳市| 阿鲁科尔沁旗|