Java编译器API

本文是我们名为“ 高级Java ”的学院课程的一部分。

本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看 !

目录

1.简介 2. Java编译器API 3.注释处理器 4.元素扫描仪 5. Java编译器树API 6.接下来 7.下载

1.简介

在本部分的教程中,我们将对Java编译器API进行10000英尺的观察。 该API提供了对Java编译器本身的编程访问,并允许开发人员从应用程序代码中即时从源文件编译Java类。

更有趣的是,我们还将遍历Java编译器树API,该API提供对Java语法分析器功能的访问。 通过使用此API,Java开发人员可以直接插入语法分析阶段并对正在编译的Java源代码进行后期分析。 它是一个非常强大的API,许多静态代码分析工具都大量使用它。

Java Compiler API还支持注释处理(有关更多详细信息,请参阅本教程的第5部分如何和何时使用Enums和Annotations ,更多内容将在本教程的第14部分Annotation Processors中提供 ),并且分为三个不同的包,如下表所示。

描述
javax.annotation.processing 注释处理。
javax.lang.model 注释处理和Compiler Tree API中使用的语言模型(包括Java语言元素,类型和实用程序类)。
javax.tools Java编译器API本身。


另一方面,Java编译器树API托管在com.sun.source package下,并且遵循Java标准库的命名约定,被认为是非标准的(专有的或内部的)。 通常,这些API没有得到很好的文档记录或支持,并且可能随时更改。 而且,它们与特定的JDK / JRE版本相关联,并且可能会限制使用它们的应用程序的可移植性。

2. Java编译器API

我们的探索将从Java Compiler API开始,该文件已被很好地记录且易于使用。 Java Compiler API的入口点是ToolProvider类 , 该类允许获取系统中可用的Java编译器实例( 官方文档是熟悉典型用法场景的一个很好的起点)。 例如:

final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();        
for( final SourceVersion version: compiler.getSourceVersions() ) {System.out.println( version );
}

此小代码段获取Java编译器实例,并在控制台上打印出受支持的Java源版本的列表。 对于Java 7编译器,输出如下所示:

RELEASE_3
RELEASE_4
RELEASE_5
RELEASE_6
RELEASE_7

它对应于多个公知的Java版本方案:1.3,1.4,5,67。 对于Java 8编译器,受支持的版本列表看起来更长:

RELEASE_3
RELEASE_4
RELEASE_5
RELEASE_6
RELEASE_7
RELEASE_8

一旦Java编译器实例可用,就可以将其用于对Java源文件集执行不同的编译任务。 但是在此之前,应准备好一组编译单元和诊断收集器(以收集所有遇到的编译错误)。 为了进行试验,我们将编译存储在SampleClass.java源文件中的这个简单Java类:

public class SampleClass {public static void main(String[] args) {System.out.println( "SampleClass has been compiled!" );}
}

创建此源文件后,让我们实例化诊断收集器并配置要编译的源文件列表(仅包含SampleClass.java )。

final DiagnosticCollector< JavaFileObject > diagnostics = new DiagnosticCollector<>();
final StandardJavaFileManager manager = compiler.getStandardFileManager( diagnostics, null, null );final File file = new File( CompilerExample.class.getResource("/SampleClass.java").toURI() );final Iterable< ? extends JavaFileObject > sources = manager.getJavaFileObjectsFromFiles( Arrays.asList( file ) );

准备工作完成后,最后一步基本上是调用Java编译器任务,将诊断收集器和源文件列表传递给它,例如:

final CompilationTask task = compiler.getTask( null, manager, diagnostics, null, null, sources );            
task.call();

基本上就是这样。 编译任务完成后,应该在target / classes文件夹中提供SampleClass.class 。 我们可以运行它以确保编译已成功执行:

java -cp target/classes SampleClass

以下输出将显示在控制台上,确认源文件已正确编译为字节码:

SampleClass has been compiled!

如果在编译过程中遇到任何错误,它们将通过诊断收集器变为可用(默认情况下,任何其他编译器输出也将被打印到System.err )。 为了说明这一点,让我们尝试编译示例Java源文件,该文件有意包含一些错误( SampleClassWithErrors.java ):

private class SampleClassWithErrors {public static void main(String[] args) {System.out.println( "SampleClass has been compiled!" );}
}

编译过程应该失败,并且可以从诊断收集器中检索错误消息(包括行号和源文件名),例如:

for( final Diagnostic< ? extends JavaFileObject > diagnostic: diagnostics.getDiagnostics() ) {System.out.format("%s, line %d in %s", diagnostic.getMessage( null ),diagnostic.getLineNumber(),diagnostic.getSource().getName() );
}

SampleClassWithErrors.java源文件上调用编译任务将在控制台上打印出以下示例错误描述:

modifier private not allowed here, line 1 in SampleClassWithErrors.java

最后但并非最不重要的一点是,为了正确完成Java Compiler API的使用,请不要忘记关闭文件管理器:

manager.close();

甚至更好的是,始终使用try-with-resources构造(本教程的第8部分“ 如何以及何时使用Exceptions”已经介绍过):

try( final StandardJavaFileManager manager = compiler.getStandardFileManager( diagnostics, null, null ) ) {// Implementation here            
}

简而言之,这些是Java Compiler API的典型使用场景。 在处理更复杂的示例时,有一些细微但非常重要的细节,可以极大地加快编译过程。 要了解更多信息,请参考官方文档 。

3.注释处理器

幸运的是,编译过程不仅限于编译。 Java Compiler支持注释处理器,可以将其视为编译器插件。 顾名思义,注释处理器可以对正在编译的代码执行加法处理(通常由注释驱动)。

在本教程的第14部分“ 注释处理器”中 ,我们将更全面地介绍注释处理器。 目前,请参阅官方文档以获取更多详细信息。

4.元素扫描仪

有时,在编译过程中,有必要对所有语言元素(类,方法/构造函数,字段,参数,变量等)进行浅层分析。 为此,Java编译器API提供了元素扫描器的概念。 元素扫描器是围绕访问者模式构建的,基本上需要实现单个扫描器(和访问者)。 为了简化实现,请提供一组基类。

我们将要开发的示例非常简单,足以展示元素扫描仪用法的基本概念,并将计算所有编译单元中的所有类,方法和字段。 基本的扫描器/访问者实现扩展了ElementScanner7类,并且仅覆盖其感兴趣的方法:

public class CountClassesMethodsFieldsScanner extends ElementScanner7< Void, Void > {private int numberOfClasses;private int numberOfMethods;private int numberOfFields;public Void visitType( final TypeElement type, final Void p ) {++numberOfClasses;return super.visitType( type, p );}public Void visitExecutable( final ExecutableElement executable, final Void p ) {++numberOfMethods;return super.visitExecutable( executable, p );}public Void visitVariable( final VariableElement variable, final Void p ) {if ( variable.getEnclosingElement().getKind() == ElementKind.CLASS ) {++numberOfFields;}return super.visitVariable( variable, p );}
}

关于元素扫描器的快速说明: ElementScannerX类家族对应于特定的Java版本。 例如, ElementScanner8对应于Java 8 , ElementScanner7对应于Java 7 , ElementScanner6对应于Java 6 ,依此类推。 所有这些类的确有一个visitXxx方法族,其中包括:

方法 描述
visitPackage 访问包元素。
visitType 访问类型元素。
visitVariable 访问变量元素。
visitExecutable 访问可执行元素。
visitTypeParameter 访问类型参数元素。

在编译过程中调用扫描器(和访问者)的一种方法是使用注释处理器。 让我们通过扩展AbstractProcessor类来定义一个(请注意,注释处理器也与特定的Java版本紧密相关,在我们的示例中为Java 7 ):

@SupportedSourceVersion( SourceVersion.RELEASE_7 )
@SupportedAnnotationTypes( "*" )
public class CountElementsProcessor extends AbstractProcessor {private final CountClassesMethodsFieldsScanner scanner;public CountElementsProcessor( final CountClassesMethodsFieldsScanner scanner ) {this.scanner = scanner;}public boolean process( final Set< ? extends TypeElement > types, final RoundEnvironment environment ) {if( !environment.processingOver() ) {for( final Element element: environment.getRootElements() ) {scanner.scan( element );}}return true;}
}

基本上,注释处理器只是将所有艰苦的工作委托给我们之前定义的扫描程序实现(在本教程的第14部分“ 注释处理器”中 ,我们将提供更全面的注释处理器示例)。

SampleClassToParse.java文件是示例,我们将在其中编译和计算所有类,方法/构造函数和字段:

public class SampleClassToParse {private String str;private static class InnerClass {     private int number;public void method() {int i = 0;try {// Some implementation here} catch( final Throwable ex ) {// Some implementation here}}}public static void main( String[] args ) {System.out.println( "SampleClassToParse has been compiled!" );}
}

编译过程看起来与我们在Java Compiler API部分中所看到的完全一样。 唯一的区别是,编译任务应配置有注释处理器实例。 为了说明这一点,让我们看一下下面的代码片段:

final CountClassesMethodsFieldsScanner scanner = new CountClassesMethodsFieldsScanner();
final CountElementsProcessor processor = new CountElementsProcessor( scanner );final CompilationTask task = compiler.getTask( null, manager, diagnostics, null, null, sources );
task.setProcessors( Arrays.asList( processor ) );
task.call();System.out.format( "Classes %d, methods/constructors %d, fields %d",scanner.getNumberOfClasses(),scanner.getNumberOfMethods(), scanner.getNumberOfFields() );

针对SampleClassToParse.java源文件执行编译任务将在控制台中输出以下消息:

Classes 2, methods/constructors 4, fields 2

这很有意义:声明了两个类, SampleClassToParseInnerClassSampleClassToParse类具有default constructor (隐式定义),方法main和字段str 。 反过来, InnerClass类也具有default constructor (隐式定义),方法method和字段number

这个例子很幼稚,但是它的目的不是展示花哨的东西,而是介绍基本概念(教程的第14部分注释处理器 ,将包括更完整的例子)。

5. Java编译器树API

元素扫描仪非常有用,但是它们提供的访问权限非常有限。 有时需要将Java源文件解析为抽象语法树(或AST )并执行更深入的分析。 Java编译器树API是我们实现它所需要的工具。 Java编译器树API与Jav​​a编译器API紧密合作,并使用javax.lang.model包。

Java编译器树API的用法与“元素扫描器”部分中的元素扫描器非常相似,并且是按照相同的模式构建的。 让我们重用Element Scanners部分中的示例源文件SampleClassToParse.java ,并计算所有编译单元中存在多少空try/catch块。 为此,我们必须通过扩展TreePathScanner基类来定义树路径扫描器(和访问者),类似于元素扫描器(和访问者)。

public class EmptyTryBlockScanner extends TreePathScanner< Object, Trees > {private int numberOfEmptyTryBlocks;@Overridepublic Object visitTry(final TryTree tree, Trees trees) {if( tree.getBlock().getStatements().isEmpty() ){++numberOfEmptyTryBlocks;}return super.visitTry( tree, trees );}public int getNumberOfEmptyTryBlocks() {return numberOfEmptyTryBlocks;}
}

与元素扫描器相比, visitXxx方法的数量要丰富visitXxx (大约50种方法),并且涵盖所有Java语言语法构造。 与元素扫描器一样,调用树路径扫描器的方法之一也是通过定义专用注释处理器,例如:

@SupportedSourceVersion( SourceVersion.RELEASE_7 )
@SupportedAnnotationTypes( "*" )
public class EmptyTryBlockProcessor extends AbstractProcessor {private final EmptyTryBlockScanner scanner;private Trees trees;public EmptyTryBlockProcessor( final EmptyTryBlockScanner scanner ) {this.scanner = scanner;}@Overridepublic synchronized void init( final ProcessingEnvironment processingEnvironment ) {super.init( processingEnvironment );trees = Trees.instance( processingEnvironment );}public boolean process( final Set< ? extends TypeElement > types, final RoundEnvironment environment ) {if( !environment.processingOver() ) {for( final Element element: environment.getRootElements() ) {scanner.scan( trees.getPath( element ), trees );}}return true;}
}

初始化过程变得有点复杂,因为我们必须获取Trees类的实例并将每个元素转换为树路径表示形式。 此时,编译步骤应该看起来非常熟悉并且足够清晰。 为了使它更具趣味性,让我们针对到目前为止我们已经尝试过的所有源文件运行它: SampleClassToParse.javaSampleClass.java

final EmptyTryBlockScanner scanner = new EmptyTryBlockScanner();
final EmptyTryBlockProcessor processor = new EmptyTryBlockProcessor( scanner );final Iterable<? extends JavaFileObject> sources = manager.getJavaFileObjectsFromFiles( Arrays.asList( new File(CompilerExample.class.getResource("/SampleClassToParse.java").toURI()),new File(CompilerExample.class.getResource("/SampleClass.java").toURI())) 
);final CompilationTask task = compiler.getTask( null, manager, diagnostics, null, null, sources );
task.setProcessors( Arrays.asList( processor ) );
task.call();System.out.format( "Empty try/catch blocks: %d", scanner.getNumberOfEmptyTryBlocks() );

一旦针对多个源文件运行,上面的代码段将在控制台中打印以下输出:

Empty try/catch blocks: 1

Java编译器树API可能看起来有点低级,的确如此。 另外,作为内部API,它没有受支持的文档。 但是,它提供了对抽象语法树的完全访问权限,当您需要执行深入的源代码分析和后处理时,它可以节省生命。

6.接下来

在本教程的这一部分中,我们研究了从Java应用程序内部对Java Compiler API的编程访问。 我们还挖掘了更深层次,更动人的注释处理器和未发现的Java编译器树API,该API提供了对正在编译的Java源文件(编译单元)的抽象语法树的完整访问。 在本教程的下一部分中,我们将以同样的方式继续,并进一步注解注释处理器及其适用性。

7.下载

这是Java编译器API的课程,是高级Java课程的第13部分。 您可以在此处下载课程的源代码: advanced-java-part-13

翻译自: https://www.javacodegeeks.com/2015/09/java-compiler-api.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/358011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

easyswoole数据库连接池_如何在 Swoole 中优雅的实现 MySQL 连接池

如何在 Swoole 中优雅的实现 MySQL 连接池一、为什么需要连接池 &#xff1f;数据库连接池指的是程序和数据库之间保持一定数量的连接不断开&#xff0c;并且各个请求的连接可以相互复用&#xff0c;减少重复连接数据库带来的资源消耗&#xff0c;一定程度上提高了程序的并发性…

CVE-2022-22965:Spring Framework远程代码执行漏洞

CVE-2022-22965&#xff1a;Spring Framework远程代码执行漏洞 本文仅为验证漏洞&#xff0c;在本地环境测试验证&#xff0c;无其它目的 漏洞编号&#xff1a; CVE-2022-22965 漏洞说明&#xff1a; Spring framework 是Spring 里面的一个基础开源框架&#xff0c;其目的是…

js中四种创建对象的方式

一、 1 var user new Object(); 2 user.first"Brad"; 3 user.last"Dayley"; 4 user.getName function( ) { return this.first " " this.last; } 二、 1 var user { 2 first: Brad, 3 last: Dayley, 4 getName: function( ) { return…

CSS系列讲解-总目录

总目录: 欢迎来到孙叫兽的《CSS系列讲解》,蓝色字体为传送门,点击进入即可。本专栏已完结,大前端专栏支持更新。 玩转CSS系列: 什么是CSS?你真的理解? CSS页面DEMO CSS基本语法? 如何玩转CSS的Id 和 Class选择器? 怎么玩转CSS内部样式表与外部样式表? 怎么样才…

红队信息收集自动化工具-水泽(ShuiZe)

红队信息收集自动化工具-水泽&#xff08;ShuiZe&#xff09; 文章目录 红队信息收集自动化工具-水泽&#xff08;ShuiZe&#xff09;0x01 介绍0x02 安装0x03 效果展示0x04 POC编写0x05 使用方法0x06 实现原理Web -> 存活探测0x07 项目地址 0x01 介绍 定位&#xff1a;协助…

注释不好吗?

前几天&#xff0c;我在有关Spring XML与注释的文章中运用了自己的原则&#xff0c;轻松进入了这个主题。 对于我目前正在编写此新应用程序的团队来说&#xff0c;这种简单的输入方式也是我不会使事情复杂化的方式&#xff0c;该应用程序的生产寿命可能为3-5年&#xff08;如果…

前端工程师应该达到什么水平,找工作薪资才比较高?

当然是水平越高&#xff0c;越容易找到工作&#xff0c;薪资越高在竞争这么激烈的2020年&#xff0c;就需要更加的努力&#xff0c;充实自己&#xff0c;让自己不被代替&#xff01;两条路&#xff1a;自学或者找培训班&#xff0c;找培训班的话&#xff0c;我推荐达内和传智播…

【转】使用JMeter对数据库做压力测试

作为一名开发人员&#xff0c;大多情况下都会认真的做好功能测试&#xff0c;但是却常常忽略了软件开发之后的压力测试&#xff0c;尤其是在面向大量用户同时使用的Web应用系统的开发过程&#xff0c;压力测试往往是不够充分的。近期我在一个求职招聘型的网站项目中就对压力测试…

python中的大数据品牌运营专业公司_国内最好的专业数据分析公司有哪些?

说说我知道的几家&#xff0c;都是在各自领域最好的。大数据平台星环&#xff0c;做Hadoop生态系列的大数据底层平台公司。也是国内唯一入选过Gartner魔力象限的大数据平台公司。Hadoop是开源的&#xff0c;星环主要做的是把Hadoop不稳定的部分优化&#xff0c;功能细化&#x…

POC以及day下载链接地址

POC以及day下载链接地址 https://github.com/Threekiii/Awesome-POC https://github.com/coffeehb/Some-PoC-oR-ExP https://github.com/PeiQi0/PeiQi-WIKI-Book https://github.com/luck-ying/Library-POC https://github.com/helloexp/0day https://github.com/BaizeSec/byl…

孙叫兽进阶之路之Gitlab的使用(图文教程)

简介&#xff1a; GitLab是一个利用 Ruby on Rails 开发的开源应用程序&#xff0c;实现一个自托管的Git项目仓库&#xff0c;可通过Web界面进行访问公开的或者私人项目。 它拥有与Github类似的功能&#xff0c;能够浏览源代码&#xff0c;管理缺陷和注释。可以管理团队对仓库的…

PyQt4(使用ui)

1.使用qt designer设计界面&#xff0c;保存为test1.ui&#xff1a; 2.使用pyuic4 test1.ui -o ui.py生成ui代码。 3.程序载入。 import sys import ui from PyQt4 import QtCore, QtGuiclass MyWidget( QtGui.QWidget ):def __init__(self):super(MyWidget, self).__init__() …

docker升级步骤及注意事项

centos系统默认安装的docker版本是1.13版本&#xff0c;在安装部分镜像时可能出现兼容问题&#xff0c;本文通过实际操作总结Docker升级最新版本步骤及可能出现的问题&#xff0c;供各位参考。 环境&#xff1a;CentOS Linux release 7.6.1810 (Core) docker升级操作&#xf…

孙叫兽进阶之路之源代码配置管理过程(图文教程)

简介:配置管理(Configuration Management,CM)是通过技术或行政手段对软件产品及其开发过程和生命周期进行控制、规范的一系列措施。配置管理的目标是记录软件产品的演化过程,确保软件开发者在软件生命周期中各个阶段都能得到精确的产品配置。

进击的Objective-C--------Objective-C基础(-)

1.面向过程和面向对象(面向对象三大特性:封装 继承 多态)面向对象编程:分析解决问题组成的对象,从中抽象出类,调用方法(协调对象间的联系与通信),解决问题.面向过程编程:分析解决问题的步骤,实现函数,一次调用2类和对象:类和对象是面向对象的核心类:具有相同特征和行为的事物的…

ivy maven_将Maven与Ivy集成

ivy maven问题是&#xff1a;您在Ivy存储库中&#xff08;只有那里&#xff09;有一些资源&#xff0c;您想在基于Maven的项目中使用这些资源。 可能的解决方案&#xff1a; 由于Ivy可以轻松使用Maven样式的存储库&#xff08;因此&#xff0c;您的Ivy客户端可以继续使用Ivy并进…

video 微信 标签层级过高_什么是微信小程序二级分销系统?如何玩转?

微信二级分销系统是通过帮助企业打造微分销商城&#xff0c;从店铺、商品、会员、分销、营销、数据分析等不同功能模块&#xff0c;让一个微信店铺焕发无限可能。微分销系统基于二级分销&#xff0c;以全员开店&#xff0c;以客推客模式迅速推动销量增长&#xff0c;快速招募微…

docker搭建简单的ctf题目

0x01 docker常用命令 1.拉取镜像。 docker pull [image] 2.查看docker当前镜像。 docker image ls 或 docker images 3.新建一个docker容器&#xff0c;并映射端口号。 docker run -d -p [host port]:[docker port] [image] 4.查看运行中的docker容器。 docker ps -a 5.进入一…

使用爱思助手备份苹果手机数据的方法

背景:前段时间刚给对象买的一个紫色的苹果11,128G的那种,最近发现电池电量忽然就少很多,电池除了点问题,去苹果售后店准备换一个电池,还在保修期,区分一下售后店(回厂修十多天)及专卖店(有备用电池)。今天提前备份一下数据,防止数据丢失,一般内存不大可以使用手机…

高级Java教程

课程大纲 学习Java基础很容易。 但是&#xff0c;真正钻研该语言并研究其更高级的概念和细微差别将使您成为一名出色的Java开发人员。 网络上充斥着“软”&#xff0c;“便宜”&#xff0c;“低端” Java教程&#xff0c;但是所缺少的实际上是将您带入新的高度的材料。 本课程…