- 浏览: 40516 次
- 性别:
- 来自: 太原
最近访客 更多访客>>
文章分类
最新评论
-
laser_lu:
我想确认一下是不是JRE下不可以,必须要JDK才行?
Java SE 6 新特性: 编译器 API
JDK 6 提供了在运行时调用编译器的 API,后面我们将假设把此 API 应用在 JSP 技术中。在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤:
- 分析 JSP 代码;
- 生成 Java 代码;
- 将 Java 代码写入存储器;
- 启动另外一个进程并运行编译器编译 Java 代码;
- 将类文件写入存储器;
- 服务器读入类文件并运行;
但如果采用运行时编译,可以同时简化步骤 4 和 5,节约新进程的开销和写入存储器的输出开销,提高系统效率。实际上,在 JDK 5 中,Sun 也提供了调用编译器的编程接口。然而不同的是,老版本的编程接口并不是标准 API 的一部分,而是作为 Sun 的专有实现提供的,而新版则带来了标准化的优点。
新 API 的第二个新特性是可以编译抽象文件,理论上是任何形式的对象 —— 只要该对象实现了特定的接口。有了这个特性,上述例子中的步骤 3 也可以省略。整个 JSP 的编译运行在一个进程中完成,同时消除额外的输入输出操作。
第三个新特性是可以收集编译时的诊断信息。作为对前两个新特性的补充,它可以使开发人员轻松的输出必要的编译错误或者是警告信息,从而省去了很多重定向的麻烦。
在 JDK 6 中,类库通过 javax.tools
包提供了程序运行时调用编译器的 API。从这个包的名字 tools 可以看出,这个开发包提供的功能并不仅仅限于编译器。工具还包括 javah、jar、pack200 等,它们都是 JDK 提供的命令行工具。这个开发包希望通过实现一个统一的接口,可以在运行时调用这些工具。在 JDK 6 中,编译器被给予了特别的重视。针对编译器,JDK 设计了两个接口,分别是 JavaCompiler
和 JavaCompiler.CompilationTask
。
下面给出一个例子,展示如何在运行时调用编译器。
- 指定编译文件名称(该文件必须在 CLASSPATH 中可以找到):
String fullQuanlifiedFileName = "compile" + java.io.File.separator +"Target.java";
- 获得编译器对象:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
通过调用 ToolProvider
的 getSystemJavaCompiler
方法,JDK 提供了将当前平台的编译器映射到内存中的一个对象。这样使用者可以在运行时操纵编译器。JavaCompiler
是一个接口,它继承了 javax.tools.Tool
接口。因此,第三方实现的编译器,只要符合规范就能通过统一的接口调用。同时,tools 开发包希望对所有的工具提供统一的运行时调用接口。相信将来,ToolProvider
类将会为更多地工具提供 getSystemXXXTool
方法。tools 开发包实际为多种不同工具、不同实现的共存提供了框架。
- 编译文件:
int result = compiler.run(null, null, null, fileToCompile);
获得编译器对象之后,可以调用 Tool.run
方法对源文件进行编译。Run
方法的前三个参数,分别可以用来重定向标准输入、标准输出和标准错误输出,null
值表示使用默认值。清单 1 给出了一个完整的例子:
清单 1. 程序运行时编译文件
01 package compile; 02 import java.util.Date; 03 public class Target { 04 public void doSomething(){ 05 Date date = new Date(10, 3, 3); // 这个构造函数被标记为deprecated, 编译时会 // 向错误输出输出信息。 06 System.out.println("Doing..."); 07 } 08 } 09 package compile; 10 import javax.tools.*; 11 import java.io.FileOutputStream; 12 public class Compiler { 13 public static void main(String[] args) throws Exception{ 14 String fullQuanlifiedFileName = "compile" + java.io.File.separator + "Target.java"; 15 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 16 FileOutputStream err = new FileOutputStream("err.txt"); 17 int compilationResult = compiler.run(null, null, err, fullQuanlifiedFileName); 18 if(compilationResult == 0){ 19 System.out.println("Done"); 20 } else { 21 System.out.println("Fail"); 22 } 23 } 24 } |
首先运行 <JDK60_INSTALLATION_DIR>\bin\javac Compiler.java,然后运行 <JDK60_INSTALLATION_DIR>\jdk1.6.0\bin\java compile.Compiler。屏幕上将输出 Done
,并会在当前目录生成一个 err.txt 文件,文件内容如下:
Note: compile/Target.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. |
仔细观察 run
方法,可以发现最后一个参数是 String...arguments
,是一个变长的字符串数组。它的实际作用是接受传递给 javac 的参数。假设要编译 Target.java 文件,并显示编译过程中的详细信息。命令行为:javac Target.java -verbose
。相应的可以将 17 句改为:
int compilationResult = compiler.run(null, null, err, “-verbose”,fullQuanlifiedFileName); |
JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler
类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager
类提供的。通常的编译过程分为以下几个步骤:
- 解析 javac 的参数;
- 在 source path 和/或 CLASSPATH 中查找源文件或者 jar 包;
- 处理输入,输出文件;
在这个过程中,JavaFileManager
类可以起到创建输出文件,读入并缓存输出文件的作用。由于它可以读入并缓存输入文件,这就使得读入各种形式的输入文件成为可能。JDK 提供的命令行工具,处理机制也大致相似,在未来的版本中,其它的工具处理各种形式的源文件也成为可能。为此,新的 JDK 定义了 javax.tools.FileObject
和 javax.tools.JavaFileObject
接口。任何类,只要实现了这个接口,就可以被 JavaFileManager
识别。
如果要使用 JavaFileManager
,就必须构造 CompilationTask
。JDK 6 提供了 JavaCompiler.CompilationTask
类来封装一个编译操作。这个类可以通过:
JavaCompiler.getTask ( Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits ) |
方法得到。关于每个参数的含义,请参见 JDK 文档。传递不同的参数,会得到不同的 CompilationTask
。通过构造这个类,一个编译过程可以被分成多步。进一步,CompilationTask
提供了 setProcessors(Iterable<? extends Processor>processors)
方法,用户可以制定处理 annotation 的处理器。图 1 展示了通过 CompilationTask
进行编译的过程:
图 1. 使用 CompilationTask 进行编译
下面的例子通过构造 CompilationTask
分多步编译一组 Java 源文件。
清单 2. 构造 CompilationTask 进行编译
01 package math; 02 public class Calculator { 03 public int multiply(int multiplicand, int multiplier) { 04 return multiplicand * multiplier; 05 } 06 } 07 package compile; 08 import javax.tools.*; 09 import java.io.FileOutputStream; 10 import java.util.Arrays; 11 public class Compiler { 12 public static void main(String[] args) throws Exception{ 13 String fullQuanlifiedFileName = "math" + java.io.File.separator +"Calculator.java"; 14 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 15 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); 16 Iterable<? extends JavaFileObject> files = fileManager.getJavaFileObjectsFromStrings( Arrays.asList(fullQuanlifiedFileName)); 17 JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, null, null, null, files); 18 Boolean result = task.call(); 19 if( result == true ) { 20 System.out.println("Succeeded"); 21 } 22 } 23 } |
以上是第一步,通过构造一个 CompilationTask
编译了一个 Java 文件。14-17 行实现了主要逻辑。第 14 行,首先取得一个编译器对象。由于仅仅需要编译普通文件,因此第 15 行中通过编译器对象取得了一个标准文件管理器。16 行,将需要编译的文件构造成了一个 Iterable
对象。最后将文件管理器和 Iterable
对象传递给 JavaCompiler
的 getTask
方法,取得了 JavaCompiler.CompilationTask
对象。
接下来第二步,开发者希望生成 Calculator
的一个测试类,而不是手工编写。使用 compiler API,可以将内存中的一段字符串,编译成一个 CLASS 文件。
清单 3. 定制 JavaFileObject 对象
01 package math; 02 import java.net.URI; 03 public class StringObject extends SimpleJavaFileObject{ 04 private String contents = null; 05 public StringObject(String className, String contents) throws Exception{ 06 super(new URI(className), Kind.SOURCE); 07 this.contents = contents; 08 } 09 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 10 return contents; 11 } 12 } |
SimpleJavaFileObject
是 JavaFileObject
的子类,它提供了默认的实现。继承 SimpleJavaObject
之后,只需要实现 getCharContent
方法。如 清单 3 中的 9-11 行所示。接下来,在内存中构造 Calculator
的测试类 CalculatorTest
,并将代表该类的字符串放置到 StringObject
中,传递给 JavaCompiler
的 getTask
方法。清单 4 展现了这些步骤。
清单 4. 编译非文本形式的源文件
01 package math; 02 import javax.tools.*; 03 import java.io.FileOutputStream; 04 import java.util.Arrays; 05 public class AdvancedCompiler { 06 public static void main(String[] args) throws Exception{ 07 // Steps used to compile Calculator 08 // Steps used to compile StringObject 09 // construct CalculatorTest in memory 10 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 11 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); 12 JavaFileObject file = constructTestor(); 13 Iterable<? extends JavaFileObject> files = Arrays.asList(file); 14 JavaCompiler.CompilationTask task = compiler.getTask ( null, fileManager, null, null, null, files); 15 Boolean result = task.call(); 16 if( result == true ) { 17 System.out.println("Succeeded"); 18 } 19 } 20 private static SimpleJavaFileObject constructTestor() { 21 StringBuilder contents = new StringBuilder( "package math;" + "class CalculatorTest {\n" + " public void testMultiply() {\n" + " Calculator c = new Calculator();\n" + " System.out.println(c.multiply(2, 4));\n" + " }\n" + " public static void main(String[] args) {\n" + " CalculatorTest ct = new CalculatorTest();\n" + " ct.testMultiply();\n" + " }\n" + "}\n"); 22 StringObject so = null; 23 try { 24 so = new StringObject("math.CalculatorTest", contents.toString()); 25 } catch(Exception exception) { 26 exception.printStackTrace(); 27 } 28 return so; 29 } 30 } |
实现逻辑和 清单 2 相似。不同的是在 20-30 行,程序在内存中构造了 CalculatorTest
类,并且通过 StringObject
的构造函数,将内存中的字符串,转换成了 JavaFileObject
对象。
第三个新增加的功能,是收集编译过程中的诊断信息。诊断信息,通常指错误、警告或是编译过程中的详尽输出。JDK 6 通过 Listener
机制,获取这些信息。如果要注册一个 DiagnosticListener
,必须使用 CompilationTask
来进行编译,因为 Tool 的 run
方法没有办法注册 Listener
。步骤很简单,先构造一个 Listener
,然后传递给 JavaFileManager
的构造函数。清单 5 对 清单 2 进行了改动,展示了如何注册一个 DiagnosticListener
。
清单 5. 注册一个 DiagnosticListener 收集编译信息
01 package math; 02 public class Calculator { 03 public int multiply(int multiplicand, int multiplier) { 04 return multiplicand * multiplier // deliberately omit semicolon, ADiagnosticListener // will take effect 05 } 06 } 07 package compile; 08 import javax.tools.*; 09 import java.io.FileOutputStream; 10 import java.util.Arrays; 11 public class CompilerWithListener { 12 public static void main(String[] args) throws Exception{ 13 String fullQuanlifiedFileName = "math" + java.io.File.separator +"Calculator.java"; 14 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 15 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); 16 Iterable<? extends JavaFileObject> files = fileManager.getJavaFileObjectsFromStrings( Arrays.asList(fullQuanlifiedFileName)); 17 DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<JavaFileObject>(); 18 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, collector, null, null, files); 19 Boolean result = task.call(); 20 List<Diagnostic<? extends JavaFileObject>> diagnostics = collector.getDiagnostics(); 21 for(Diagnostic<? extends JavaFileObject> d : diagnostics){ 22 System.out.println("Line Number->" + d.getLineNumber()); 23 System.out.println("Message->"+ d.getMessage(Locale.ENGLISH)); 24 System.out.println("Source" + d.getCode()); 25 System.out.println("\n"); 26 } 27 if( result == true ) { 28 System.out.println("Succeeded"); 29 } 30 } 31 } |
在 17 行,构造了一个 DiagnosticCollector
对象,这个对象由 JDK 提供,它实现了 DiagnosticListener
接口。18 行将它注册到 CompilationTask
中去。一个编译过程可能有多个诊断信息。每一个诊断信息,被抽象为一个 Diagnostic
。20-26 行,将所有的诊断信息逐个输出。编译并运行 Compiler,得到以下输出:
清单 6. DiagnosticCollector 收集的编译信息
Line Number->5 Message->math/Calculator.java:5: ';' expected Source->compiler.err.expected |
实际上,也可以由用户自己定制。清单 7 给出了一个定制的 Listener
。
清单 7. 自定义的 DiagnosticListener
01 class ADiagnosticListener implements DiagnosticListener<JavaFileObject>{ 02 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 03 System.out.println("Line Number->" + diagnostic.getLineNumber()); 04 System.out.println("Message->"+ diagnostic.getMessage(Locale.ENGLISH)); 05 System.out.println("Source" + diagnostic.getCode()); 06 System.out.println("\n"); 07 } 08 } |
JDK 6 的编译器新特性,使得开发者可以更自如的控制编译的过程,这给了工具开发者更加灵活的自由度。通过 API 的调用完成编译操作的特性,使得开发者可以更方便、高效地将编译变为软件系统运行时的服务。而编译更广泛形式的源代码,则为整合更多的数据源及功能提供了强大的支持。相信随着 JDK 的不断完善,更多的工具将具有 API 支持,我们拭目以待。
相关推荐
Java SE 6 新特性 编译器 API
在 Java SE6 中,JavaCompiler 接口是 javax.tools 包的一部分,提供了标准的方式来操作 Java 编译器。 使用 JavaCompiler 接口来编译 Java 源程式是一种非常简单的方法。首先,我们可以通过 ToolProvider 类的静态...
本书包含有关 StAX API、JDBC 4、编译器API、脚本框架、闪屏和托盘API以及其他许多Java SE6改进特性的新章节。本书聚焦于Java语言中更高级的特性,包括下列内容:流与文件、网络、数据库编程、XML、JNDI与LDAP、国际...
本书包含有关StAX API、JDBC 4、编译器API、脚本框架、闪屏和托盘API以及其他许多Java SE6改进特性的新章节。本书聚焦于Java语言中更高级的特性,包括下列内容:流与文件、网络、数据库编程、XML、JNDI与LDAP、国际...
### Java SE 6: Top 10 Features #### 1. Scripting支持 在Java SE 6中,Scripting的支持成为了一个重要的新功能。这一功能的引入为开发者提供了结合不同语言优势的机会,允许他们在同一应用中混合使用多种编程...
对于开发工具,例如编译器API,XML处理API等也得到了更新,其中JDBC4.0的引入使得JDBC API能够利用Java SE 8中的新特性,比如默认方法。同时,可插拔的注解处理API(JSR269)的更新,简化了注解的处理方式,使得处理...
Java SE(标准版)6 API是Java开发的关键资源,它为开发者提供了丰富的类库和接口,使得构建桌面应用、服务器端应用以及网络应用变得更为便捷。这个API文档详细阐述了Java 6平台的核心功能,包括核心类库、基础工具...
本书包含有关 StAX API、JDBC 4、编译器API、脚本框架、闪屏和托盘API以及其他许多Java SE6改进特性的新章节。本书聚焦于Java语言中更高级的特性,包括下列内容:流与文件、网络、数据库编程、XML、JNDI与LDAP、国际...
Java EE 6为企业级应用开发提供了强大的工具和框架,而Java SE 8则通过引入新特性提升了开发者的生产力和代码质量。对于Java开发者来说,理解和掌握这两个版本的核心特性是必不可少的。通过深入学习和实践,可以提升...
3. **Stream API**:Stream API是Java 8的新特性,提供了一种对集合数据进行操作的全新方式。它支持串行和并行操作,如过滤、映射、排序、聚合等,提高了代码的可读性和效率。 4. **日期和时间API**:Java 8通过...
Java SE(标准版)API(应用程序接口)是Java编程语言的核心组成部分,它为开发者提供了大量预定义的类和方法,使得开发Java应用变得更加便捷。这个API文档,"JAVA SE API HELP",是一个重要的学习资源,它包含了...
Java SE 8的发布标志着Java语言的重大变革,引入了许多新特性,提升了性能和开发效率。 1. **Lambda表达式**:Java SE 8引入了Lambda表达式,这是一种简洁的匿名函数写法,能够简化处理函数接口实例的代码。Lambda...
Java SE 11是Java平台的重大更新之一,引入了许多新特性、改进和优化。在API方面,以下是一些关键的知识点: 1. **模块系统(Module System)**:Java 9引入了模块系统,Java 11进一步完善。模块化有助于构建大型...
通过阅读它们,你可以深入了解每个版本的新特性和API的使用方法。尽管是英文版,但它们通常比中文翻译更准确,对于深入理解Java技术是不可或缺的。在开发过程中,对照这些文档可以快速查找到类、方法和接口的详细...
一、Java SE6的新特性 1. 更强的并发性能:Java SE6引入了并发工具包`java.util.concurrent`,包含如`ExecutorService`、`Future`、`Callable`等接口和类,极大地提高了多线程编程的效率和可控性。 2. 动态语言...
这个文档包含了Java SE(标准版)8的所有公共API,是理解和使用新特性的关键工具。让我们深入探讨Java 8 API中的一些核心概念和重要特性。 1. **Lambda表达式** Java 8引入了lambda表达式,这是一种简洁的函数式...
Java 1.8 API是Java开发的关键组成部分,它包含了Java标准版(Java SE)1.8版本的所有核心类库和接口。这些类库为开发者提供了丰富的功能,支持从基本的数据类型操作到复杂的网络编程、多线程处理以及数据库连接等。...
1. **改进的性能**:Java SE 6通过优化垃圾回收机制、JIT编译器和内存管理提高了整体性能,使得应用程序运行更加流畅。 2. **动态语言支持**:引入了JSR 223(Scripting for the Java Platform),这使得Java平台...
3. **Java API文档**:这是文档中最核心的部分,提供了所有Java SE 6库的类和接口的详细说明,包括`java.lang`、`java.util`、`java.io`、`java.net`、`java.sql`等包,涵盖了I/O、集合、网络编程、数据库连接、日期...