成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Java 編譯器 javac 筆記:javac API、注解處理 API 與 Lombok 原理

lookSomeone / 1572人閱讀

摘要:對(duì)語法樹的掃描,同樣提供了掃描器。詞法分析過程如下圖所示語法分析,即根據(jù)語法由序列生成抽象語法樹,對(duì)應(yīng)實(shí)現(xiàn)類為。生成的抽象語法樹如下圖所示的實(shí)現(xiàn)原理依賴開發(fā)的典型的第三方庫(kù)有,代碼自動(dòng)生成的和,代碼檢查的和,編譯階段完成依賴注入的等。

原文:http://nullwy.me/2017/04/java...
如果覺得我的文章對(duì)你有用,請(qǐng)隨意贊賞

javac 是 Java 代碼的編譯器 [openjdk, oracle ],初學(xué) Java 的時(shí)候就應(yīng)該接觸過。本筆記整理一些 javac 相關(guān)的高級(jí)用法。

javac 命令行

javac 命令行工具,官方文檔有完整的使用說明,doc。當(dāng)然也可以,運(yùn)行 javac -helpman javac 查看幫助信息。下面是經(jīng)典的 hello world 代碼:

package com.test.javac;
public class Hello {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

編譯與運(yùn)行

$ tree   # 代碼目錄結(jié)構(gòu)
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── test
        │           └── javac
        │               └── Hello.java
        └── resources
$ mkdir -p target/classes   # 創(chuàng)建 class 文件的存放目錄
$ javac src/main/java/com/test/javac/Hello.java -d target/classes
$ java -cp "target/classes" com.test.javac.Hello 
hello world 
javac 相關(guān) API

除了使用命令行工具編譯 Java 代碼,JDK 6 增加了規(guī)范 JSR-199 和 JSR-296,開始還提供相關(guān)的 API。Java 編譯器的實(shí)現(xiàn)代碼和 API 的整體結(jié)構(gòu)如圖所示[doc]:

綠色標(biāo)注的包是官方 API(Official API),即 JSR-199 和 JSR-296,黃色標(biāo)注的包為(Supported API),紫色標(biāo)注的包代碼全部在 com.sun.tools.javac.* 包下,為內(nèi)部 API(Internal API)和編譯器的實(shí)現(xiàn)類。完整的包說明如下:

javax.annotation.processing - 注解處理 (JSR-296)

javax.lang.model - 注解處理和編譯器 Tree API 使用的語言模型 (JSR-296)

javax.lang.model.element - 語言元素

javax.lang.model.type - 類型

javax.lang.model.util - 語言模型工具

javax.tools - Java 編譯器 API (JSR-199)

com.sun.source.* - 編譯器 Tree API,提供 javac 工具使用的抽象語法樹 AST 的只讀訪問

com.sun.tools.javac.* - 內(nèi)部 API 和編譯器的實(shí)現(xiàn)類

全部源碼都位于 langtools 下,在 JDK 中的 tools.jar 可以找到。com.sun.tools.javac.* 包下全部代碼中都有Sun標(biāo)注的警告:

This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are subject to change or deletion without notice.
Java 編譯器 API

首先,看下 JSR-199 引入的 Java 編譯器 API。在沒有引入 JSR-199 前,只能使用 javac 源碼提供內(nèi)部 API,上文提到的使用命令 javac 編譯 Hello.java 的等價(jià)寫法如下:

import com.sun.tools.javac.main.Main;

public class JavacMain {
    public static void main(String[] args) {
        Main compiler = new Main("javac");
        compiler.compile(new String[]{"src/main/java/com/test/javac/Hello.java", "-d", "target/classes"});
    }
}

JSR-199 的等價(jià)寫法:

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Arrays;

public class Jsr199Main {
    public static void main(String[] args) throws URISyntaxException, IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector diagnostics = new DiagnosticCollector<>();

        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);

        File file = new File("src/main/java/com/test/javac/Hello.java");
        Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(file));

        compiler.getTask(null, fileManager, diagnostics, Arrays.asList("-d", "target/classes"), null, compilationUnits).call();

        for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
            System.out.format("Error on line %d in %s
%s
",
                    diagnostic.getLineNumber(), diagnostic.getSource().toUri(), diagnostic.getMessage(null));
        }

        fileManager.close();
    }
}
可插拔式注解處理 API

JSR-269(Pluggable Annotation Processing API)。要理解注解處理,需要先了解 Java 代碼的編譯過程,編譯過程如下圖所示 [doc]:

整個(gè)過程就是

源代碼經(jīng)過詞法解析和語法解析,生成語法樹。然后將遇到的類符號(hào)以及在類內(nèi)部定義的符號(hào)填充入(enter)符號(hào)表。

所有注解處理器會(huì)被處理,若處理器生成新的代碼或 class 文件,編譯過程會(huì)重新開始,直到?jīng)]有新的文件生成。

語義分析和代碼生成,即類型檢查、控制流分析、泛型的類型擦除、去除語法糖、字節(jié)碼生成等操作。

代碼示例:

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("*")
public class VisitProcessor extends AbstractProcessor {

    private MyScanner scanner;

    @Override
    public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.scanner = new MyScanner();
    }

    public boolean process(Set types, RoundEnvironment environment) {
        if (!environment.processingOver()) {
            for (Element element : environment.getRootElements()) {
                scanner.scan(element);
            }
        }
        return true;
    }

    public class MyScanner extends ElementScanner7 {

        public Void visitType(TypeElement element, Void p) {
            System.out.println("類 " + element.getKind() + ": " + element.getSimpleName());
            return super.visitType(element, p);
        }

        public Void visitExecutable(ExecutableElement element, Void p) {
            System.out.println("方法 " + element.getKind() + ": " + element.getSimpleName());
            return super.visitExecutable(element, p);
        }

        public Void visitVariable(VariableElement element, Void p) {
            if (element.getEnclosingElement().getKind() == ElementKind.CLASS) {
                System.out.println("字段 " + element.getKind() + ": " + element.getSimpleName());
            }
            return super.visitVariable(element, p);
        }
    }
}

編譯器 API 的 CompilationTasksetProcessors 方法可以傳入注解處理器,代碼如下(被編譯的 java 文件就是 VisitProcessor.java):

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector diagnostics = new DiagnosticCollector<>();
VisitProcessor processor = new VisitProcessor();

StandardJavaFileManager manager = compiler.getStandardFileManager(diagnostics, null, null);
File file = new File("src/main/java/com/test/proc/visit/VisitProcessor.java");
Iterable sources = manager.getJavaFileObjectsFromFiles(Arrays.asList(file));

CompilationTask task = compiler.getTask(null, manager, diagnostics, Arrays.asList("-d", "target/classes"), null, sources);
task.setProcessors(Arrays.asList(processor));
task.call();

manager.close();

或者也通過 javac 命令編譯,指定注解處理器通過 -processor 參數(shù)選項(xiàng)。另外,若 classpath 中存在目錄 META-INF/services/(或 jar 包中存在),并有 javax.annotation.processing.Processor 文件,在該文件中填寫的注解處理器類名(多個(gè)的話,換行填寫),編譯器就會(huì)自動(dòng)使用這下填寫的注解處理器進(jìn)行注解處理。

運(yùn)行輸出結(jié)果如下:

類 CLASS: VisitProcessor
類 CLASS: MyScanner
方法 CONSTRUCTOR: 
方法 METHOD: visitType
方法 METHOD: visitExecutable
方法 METHOD: visitVariable
方法 CONSTRUCTOR: 
字段 FIELD: scanner
方法 METHOD: init
方法 METHOD: process

可以看到整個(gè)類文件被掃描,包括內(nèi)部類以及全部方法、構(gòu)造方法和字段。注解處理在填充符號(hào)表之后進(jìn)行,ElementScanner 類掃描的 Element 其實(shí)就是符號(hào) Symbol。從 Symbol 類的定義可以看到這一點(diǎn)。

public abstract class Symbol extends AnnoConstruct implements Element

填充符號(hào)表前一步是構(gòu)造語法樹。對(duì)語法樹的掃描,com.sun.source.* 同樣提供了掃描器TreeScanner。使用 TreeScanner 掃描 java 代碼的示例代碼如下所示:

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("*")
public class VisitTreeProcessor extends AbstractProcessor {
    private Trees trees;
    private MyScanner scanner;

    @Override
    public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.trees = Trees.instance(processingEnv);
        this.scanner = new MyScanner();
    }

    public boolean process(Set types, RoundEnvironment environment) {
        if (!environment.processingOver()) {
            for (Element element : environment.getRootElements()) {
                TreePath path = trees.getPath( element );
                scanner.scan(path, null);
            }
        }
        return true;
    }

    public class MyScanner extends TreePathScanner {

        public Tree visitClass(ClassTree node, Void p) {
            System.out.println("類 " + node.getKind() + ": " + node.getSimpleName());
            return super.visitClass(node, p);
        }

        public Tree visitMethod(MethodTree node, Void p) {
            System.out.println("方法 " + node.getKind() + ": " + node.getName());
            return super.visitMethod(node, p);
        }

        public Tree visitVariable(VariableTree node, Void p) {
            if (this.getCurrentPath().getParentPath().getLeaf() instanceof ClassTree) {
                System.out.println("字段 " + node.getKind() + ": " + node.getName());
            }
            return super.visitVariable(node, p);
        }
    }
}

運(yùn)行輸出結(jié)果如下:

類 CLASS: VisitTreeProcessor
方法 METHOD: 
字段 VARIABLE: trees
字段 VARIABLE: scanner
方法 METHOD: init
方法 METHOD: process
類 CLASS: MyScanner
方法 METHOD: 
方法 METHOD: visitClass
方法 METHOD: visitMethod
方法 METHOD: visitVariable

需要注意的是,獲取語法樹是通過工具類 Trees 的 getTree 方法完成的。另外,可以看到 com.sun.source.* 包下暴露的 API 對(duì)語法樹只能做只讀操作,功能有限,要想修改語法樹必須使用 javac 的內(nèi)部 API。

javac 內(nèi)部 API

針對(duì)語句 int y = x + 1; 的詞法分析,即根據(jù)詞法將字符序列轉(zhuǎn)換為 token 序列,對(duì)應(yīng)實(shí)現(xiàn)類為 com.sun.tools.javac.parser.Scanner。詞法分析過程如下圖所示 ref [RednaxelaFX ]:

語法分析,即根據(jù)語法由 token 序列生成抽象語法樹,對(duì)應(yīng)實(shí)現(xiàn)類為 com.sun.tools.javac.parser.Parser。生成的抽象語法樹如下圖所示:

Lombok 的實(shí)現(xiàn)原理

依賴 JSR-269 開發(fā)的典型的第三方庫(kù)有,代碼自動(dòng)生成的 Lombok 和 Google Auto,代碼檢查的 Checker 和 Google Error Prone,編譯階段完成依賴注入的 Google Dagger 2 等。

現(xiàn)在看下 Lombok 的實(shí)現(xiàn)源碼。Lombok 提供 @NonNull, @Getter, @Setter, @ToString, @EqualsAndHashCode, @Data等注解,自動(dòng)生成常見樣板代碼 boilerplate,解放開發(fā)效率。Lombok 支持 javac 和 ecj (Eclipse Compiler for Java)。對(duì)于 javac 編譯器對(duì)應(yīng)的注解處理器是 LombokProcessor,然后經(jīng)過一些處理過程,每個(gè)注解都會(huì)有特定的 handler 來處理,@NonNull 對(duì)應(yīng) HandleNonNull、@Getter 對(duì)應(yīng) HandleGetter、@Setter 對(duì)應(yīng) HandleSetter、@ToString 對(duì)應(yīng) HandleToString、@EqualsAndHashCode 對(duì)應(yīng)HandleEqualsAndHashCode、@Data 對(duì)應(yīng) HandleData。閱讀這些 handler 的實(shí)現(xiàn),可以看到樣板代碼的生成依賴的就是 com.sun.tools.javac.* 包。

為了試驗(yàn)和學(xué)習(xí) javac 內(nèi)部 API 的功能,本人嘗試重新實(shí)現(xiàn) Lombok 的 @Data 注解,簡(jiǎn)單實(shí)現(xiàn)了自動(dòng)生成 getter 和 setter 的功能,代碼參見 github,使用 @Data 的代碼見 link。

參考資料

The Java programming language Compiler Group http://openjdk.java.net/group...

2008-03 The Hacker"s Guide to Javac http://scg.unibe.ch/archive/p...

2015-09 Java Compiler API https://www.javacodegeeks.com...

2015-09 Java Annotation Processors https://www.javacodegeeks.com...

2011-05 How does lombok work? http://stackoverflow.com/q/61...

莫樞 RednaxelaFX :JVM分享——Java程序的編譯、加載與執(zhí)行 http://www.valleytalk.org/201...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/74629.html

相關(guān)文章

  • Lombok介紹、使用方法和總結(jié)

    摘要:使用方法能以簡(jiǎn)單的注解形式來簡(jiǎn)化代碼,提高開發(fā)人員的開發(fā)效率。能通過注解的方式,在編譯時(shí)自動(dòng)為屬性生成構(gòu)造器方法。出現(xiàn)的神奇就是在源碼中沒有和方法,但是在編譯生成的字節(jié)碼文件中有和方法。沒法實(shí)現(xiàn)多種參數(shù)構(gòu)造器的重載。 1 Lombok背景介紹 官方介紹如下: Project Lombok makes java a spicier language by addi...

    30e8336b8229 評(píng)論0 收藏0
  • 使用神器Lombok優(yōu)雅編碼

    摘要:提高編碼效率使代碼更簡(jiǎn)潔消除冗長(zhǎng)代碼避免修改字段名字時(shí)忘記修改方法名提高下逼格以上就是的優(yōu)點(diǎn),當(dāng)然,的優(yōu)點(diǎn)遠(yuǎn)遠(yuǎn)不止以上幾點(diǎn),使用,你可以更加優(yōu)雅高效的編輯代碼。實(shí)戰(zhàn)完成了上述準(zhǔn)備之后,就可以愉快的使用進(jìn)行編碼了。接下來是使用簡(jiǎn)化后的代碼。 Lombok介紹 近來偶遇一款擼碼神器,介紹給大家~相信許多小伙伴都深有體會(huì),POJO類中的千篇一律的getter/setter,construct...

    _ang 評(píng)論0 收藏0
  • 使用lombok來簡(jiǎn)化你的Java Bean

    摘要:可標(biāo)注在類內(nèi)部生成一個(gè)名為類名的內(nèi)部類,用于快速構(gòu)建。流程是這樣的編譯源代碼,并生成語法樹尋找實(shí)現(xiàn)了的代碼,并調(diào)用。尋找被標(biāo)注了注解的類,修改生成的語法樹。將語法樹生成為字節(jié)碼就到這里了它還具備很多好用的功能,你可以去這里看看。 能做什么? 在使用lombok之前: public class Book { private Integer id; private St...

    taowen 評(píng)論0 收藏0
  • 途牛原創(chuàng)|使用 lombok 簡(jiǎn)化 Java 代碼

    摘要:使用,簡(jiǎn)化代碼為了簡(jiǎn)化與,提供了一種機(jī)制,幫助我們自動(dòng)生成這些樣板代碼。但是,在實(shí)際項(xiàng)目中,完全沒有使用到。源碼審查是一個(gè)源碼審查工具。最新版已經(jīng)支持的全部注解,不再認(rèn)為是沒有使用的變量。 一個(gè)典型的 Java 類 public class A { private int a; private String b; public int getA() { ret...

    RyanHoo 評(píng)論0 收藏0
  • Java編譯期優(yōu)化思維導(dǎo)圖

    摘要:本文參考自來自周志明深入理解虛擬機(jī)第版,拓展內(nèi)容建議讀者可以閱讀下這本書。和構(gòu)造方法一一對(duì)應(yīng),是同一概念在兩個(gè)級(jí)別的含義收斂的操作自動(dòng)保證執(zhí)行父類的執(zhí)行語句塊初始化類變量字符串加操作替換為或的操作 showImg(https://segmentfault.com/img/remote/1460000016240419?w=3876&h=3614); 本文參考自來自周志明《深入理解Jav...

    sorra 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<