JAVAC原理

前言

本文是对 compilation-overview 的翻译. 如有翻译不对的地方,还望海涵.

正文

将一组源文件编译成相应的一组类文件的过程并不简单,但是通常可以分为三个阶段。源文件的不同部分可以在“按需”的基础上以不同的速率进行处理。

javac原理

这个过程是由JavaCompiler类来处理的:

  1. 将命令行上指定的源文件进行读取,解析为语法树,然后将所有外部可见的定义都输入到编译器的符号表中
  2. 在编译的过程中会调用适当的注解处理器.如果在调用过程中,生成了新的资源文件或者类文件,则重新进行编译,直到没有新文件创建为止
  3. 最后,对语法分析器生成的语法树进行分析,并将其转换为类文件。在分析过程中,可以找到对其他类的引用。编译器将检查这些类的源和类路径;如果在源路径上找到它们,那么这些文件也将被编译,尽管它们不会受到注释处理的影响。

解析和输入

源文件是被Scanner按照Unicode 编码来处理的同时转化为token流.

token流由解析器读取,使用TreeMaker创建语法树。语法树是从JCTree的子类型构建的,它实现了com.sun.source.Tree 和它的子类.

每一个树都被交给Enter类来进行处理,它会将所遇到的符号都输入到符号表中.这必须在分析树是否会引用这些符号前完成.这个阶段的输出是一个待办事项列表(TO DO list),包含需要分析并生成类文件的树.

输入由多个阶段组成;类通过队列从一个阶段迁移到下一个阶段.

Enter

  1. 在第一个阶段,所有类符号都被输入到它们的封闭范围(enclosing scope,也许可以翻译为定义域?)中,对于内部类,它们沿着树递归下降的输入到封闭范围。类符号被赋予一个成员对象作为完成符。
    此外,如果找到包含包注释的package-info.java文件,则该文件的顶级树节点也放在To Do列表中

  2. 在第二个阶段,类通过MemberEnter.complete()方法来进行处理(completed , 使类变得完整).使类变得完整可能是按需发生的,但是任何未以这种方式完成的类最终将通过处理未完成队列来完成.使一个类完成需要如下条件:

    1. 确定类的泛型参数,父类,接口
    2. 将该类的所有符号输入到它所对应的scope,当该类在第一点已经输入时会产生错误.

    第2点依赖于第一点已经完成了一个类及其所有的超类和外部类.这就是为什么在做(1)之后,我们把类放在一个半完成的队列中。只有当我们对一个类进行了(1)和它的所有超类和外部类时,我们才进行(2).

  3. 在输入所有符号之后,将对这些符号上遇到的任何注解进行分析和验证。

虽然第一阶段被组织成扫描所有编译的语法树,但第二阶段是按需进行的。类的成员是在第一次访问类的内容时输入的。这是通过在编译后的类的类符号中安装completer对象来实现的,编译后的类为对应的类树调用MemberEnter阶段。

注解处理

这部分是由JavacProcessingEnvironment 来进行处理的

从概念上讲,注释处理是编译前的一个准备步骤.这个准备步骤包括一系列循环,每个循环用于解析和输入源文件,然后确定和调用任何适当的注解处理器.在第一次循环之后,如果任何被调用的注解处理器生成任何需要成为最终编译一部分的新源文件或类文件,则将执行后续循环。最后,当完成所有必要的循环时,执行实际编译。

3

实际上,在解析要编译的文件并确定它们包含的声明之前,可能不知道需要调用哪个注解处理器。因此,为了避免在没有执行注释处理的情况下不必要地解析和输入源文件,JavacProcessingEnvironment与概念模型执行“不同步”,同时仍然满足注解处理作为一个整体在实际编译之前发生的概念要求。

4

JavacProcessingEnvironment在文件已经被解析并输入之后被调用。它决定是否需要加载任何注解处理器,并调用任何正在编译的文件.通常,如果在整个编译过程中出现任何错误,则在下一个convenient point停止该过程.但是,如果在Enter阶段检测到任何丢失的符号,则会发生异常,因为调用注解处理器可以生成这些符号的定义。

如果要运行注解处理器,则在单独的类加载器加载并运行它们.

在运行注解处理器时,JavacProcessingEnvironment确定是否需要另一轮注解处理.如果是,它创建一个新的JavaCompiler对象,读取需要解析的任何新生成的源文件,并重用任何以前解析过的语法树。所有这些树都被输入到这个新编译器实例的符号表中,并在必要时调用注解处理器。重复这一步骤,直到不再需要更多的注解处理。

最后,JavacProcessingEnvironment返回用于编译其余部分的JavaCompiler对象。这将是用于解析和输入初始文件集的原始实例,或者它将是用于开始最后一轮编译JavacProcessingEnvironment创建的最新实例.

分析和生成

一旦命令行上指定的所有文件都被解析并输入到编译器的符号表中,并且注解处理完之后,JavaCompiler就可以继续分析被解析的语法树,以便生成相应的类文件.

在分析树时,可以找到对成功编译所需的类(但未明确指定用于编译的类)的引用。根据编译选项,将根据搜索源路径和类路径来搜索这些类的定义.如果定义在类文件中找到,则读取类文件以确定该类中的定义;如果定义在源文件中找到,则源文件将自动解析、输入并放到“待办事项”列表中。这是通过注册JavaCompiler作为Attr.SourceCompleter的一个实现来完成的。

分析树和生成类文件的工作是由一系列visitor 执行的,这些访问者处理编译器的To Do列表上的条目。唯一的要求是,To Do列表中的每个条目最终都应该由这些visitor来处理,除非由于错误而提前终止编译。

Attr(属性标记)

顶层类认为是"属性标记过的",当使用Attr,将语法树中的名称、表达式和其他元素被解析并与相应的类型和符号相关联.许多语义错误可以在这里被检测,无论是通过Attr,还是通过检查。

Flow(数据流分析)

如果到目前为止还没有错误,则使用数据流分析对类进行分析。数据流分析用于检查变量的确定分配和不可到达的语句,这可能导致额外的错误 

TransTypes

使用TransTypes将泛型类型的代码转换为没有泛型类型的代码,

Lower(强度削弱)

“语法糖”是使用Lower进行处理的,它重写语法树以通过替换等价的、简单的树来消除特定类型的子树。这需要处理嵌套和内部类、类中的字面量、断言、Frach循环等等。对于处理的每个类,Lower返回已转换的类及其所有已转换的嵌套类和内部类的树列表

虽然Lower通常处理顶级类,但它也将处理顶级的package-info.java。对于这样的树,Lower将创建一个合成类来包含包的任何注解

Gen(字节码生成)

方法的代码由Gen生成,它创建包含JVM执行方法所需的字节码的Code属性。如果这一步成功了,则这个类是由ClassWriter写出来的

一旦类被写成一个类文件,就不再需要它的语法树和生成的字节码。为了节省内存,对树和符号的这些部分的引用将被取消,以允许垃圾收集器恢复内存。

猜你喜欢

转载自blog.csdn.net/qq_26000415/article/details/82257763
今日推荐