20230706 8. 脚本、编译与注解处理
脚本、编译与注解处理
脚本 API 使你可以调用诸如 JavaScript 和 Groovy 这样的脚本语言代码;当你希望在应用程序内部编译 Java 代码时,可以使用编译器 API ;注解处理器可以在包含注解的 Java 源代码和类文件上进行操作。如你所见,有许多应用程序都可以用来处理注解,从简单的诊断到“字节码工程”,后者可以将字节码插入到类文件中,甚至可以插入到运行程序中
Java 平台的脚本
脚本语言是一种通过在运行时解释程序文本,从而避免使用通常的编辑 编译/链接 /运行循环的语言。脚本语言有许多优势:
- 便于快速变更,鼓励不断试验
- 可以修改运行着的程序的行为
- 支持程序用户的定制化
另一方面,大多数脚本语言都缺乏可以使编写复杂应用受益的特性,例如强类型、封装和模块化
人们在尝试将脚本语言和传统语言的优势相结合。脚本 API 使你可以在 Java 平台上实现这个目的,它支持在 Java 程序中对用 JavaScript 、Groovy 、Ruby ,甚至是更奇异的诸如 Scheme 和 Haskell 等语言编写的脚本进行调用
获取脚本引擎
脚本引擎是一个可以执行用某种特定语言编写的脚本的类库。当虚拟机启动时,它会发现可用的脚本引擎。为了枚举这些引擎,需要构造一个 ScriptEngineManager
,并调用 getEngineFactories
方法。可以向每个引擎工厂询问它们所支持的引擎名、MIME 类型和文件扩展名
脚本引擎工厂的属性:
引擎 | 名字 | MIME 类型 | 文件扩展 |
---|---|---|---|
Rhino (Javascript) | rhino, Rhino, JavaScript, javascript | application/javascript, application/ecmascript, text/javascript, text/ecmascript | js |
Groovy | groovy | 无 | groovy |
Renjin | Renjin | text/x-R | R, r, S, s |
通常,你知道所需要的引擎,因此可以直接通过名字 MIME 类型或文件扩展来请求它。Oracle JDK 以往都包含一个 JavaScript 引擎,但是在 Java 15 中被移除了
Rhino 没有遵守 Java 标准,而是使用自己的一套 API
引入 Nashorn
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
Object eval = engine.eval("1+8");
System.out.println(eval); // 9
可以通过在类路径中提供必要的 JAR 文件来添加对更多语言的支持
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> engineFactories = manager.getEngineFactories();
for (ScriptEngineFactory engineFactory : engineFactories) {
System.out.println(engineFactory.getEngineName());
System.out.println(engineFactory.getEngineVersion());
System.out.println(engineFactory.getNames());
}
/*
Oracle Nashorn
1.8.0_302
[nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
*/
javax.script.ScriptEngineManager 方法名称 |
方法声明 | 描述 |
---|---|---|
getEngineFactories |
public List<ScriptEngineFactory> getEngineFactories() |
获取所有发现的引擎工厂的列表 |
getEngineByName getEngineByExtension getEngineByMimeType |
public ScriptEngine getEngineByName(String shortName) public ScriptEngine getEngineByExtension(String extension) public ScriptEngine getEngineByMimeType(String mimeType) |
获取给定名字、脚本文件扩展名或阳ME 类型的脚本引擎 |
javax.script.ScriptEngineFactory 方法名称 |
方法声明 | 描述 |
---|---|---|
getNames getExtensions getMimeTypes |
public List<String> getNames(); public List<String> getExtensions(); public List<String> getMimeTypes(); |
获取该工厂所了解的名字、脚本文件扩展名和 MIME 类型 |
脚本赋值与绑定
一旦拥有了引擎,就可以通过下面的调用来直接调用脚本:
Object result = engine.eval(scriptString);
重定向输入和输出
可以通过调用脚本上下文的 setReader
和 setWriter
方法来重定向脚本的标准输入和输出
StringWriter writer = new StringWriter();
engine.getContext().setWriter(new PrintWriter(writer, true));
任何 JavaScript 的 print
和 println
函数产生的输出都会被发送到 writer
。 setReader
和 setWriter
方法只会影响脚本引擎的标准输入和输出源
Nashorn 引擎没有标准输入源的概念,因此调用 setReader
没有任何效果
调用脚本的函数和方法
在使用许多脚本引擎时,都可以调用脚本语言的函数,而不必对实际的脚本代码进行计算。
提供这种功能的脚本引擎实现了 Invocable
接口。特别是, Nashorn 引擎就是实现了 Invocable
接口
编译脚本
某些脚本引擎出于对执行效率的考虑,可以将脚本代码编译为某种中间格式。这些引擎实现了 Compilable
接口
当然,只有需要重复执行时,我们才希望编译脚本
编译器 API
调用编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
OutputStream outStream = null;
OutputStream errrStream = null;
int result = compiler.run(null, outStream, errrStream, "-sourcepath", "src", "Test.java");
System.out.println(result);
返回值为 0
表示编译成功
编译器会向提供给它的流发送输出和错误消息。如果将这些参数设置为 null
,就会使用 System.out
和 System.err
。run
方法的第一个参数是输入流,由于编译器不会接受任何控制台输入,因此总是应该让其保持为 null
。( run
方法是 Tool
接口继承而来的,它考虑到某些工具需要读取输入)
如果在命令行调用 javac
,那么 run
方法其余的参数就会作为变量传递给 javac
。这些变量是一些选项或文件名
发起编译任务
可以通过使用 CompilationTask
对象来对编译过程进行更多的控制。特别是,你可以:
- 控制程序代码的来源,例如,在字符串构建器而不是文件中提供代码
- 控制类文件的放置位置,例如,存储在数据库中
- 监听在编译过程中产生的错误和警告信息
- 在后台运行编译器
捕获诊断信息
javax.tools.DiagnosticListener
从内存中读取源文件
略
将字节码写出到内存中
略