关于Java Scripting API您不知道的5件事,Java平台上更简朴的剧本编写要领
副标题#e#
此刻,很多Java开拓人员都喜欢在Java平台中利用剧本语言,可是利用编译到 Java 字节码中的动态语言有时是不行行的。在某些环境中,直接编写一个 Java 应用措施的剧本 部门 可能在一个剧本中挪用特定的 Java 工具是更快捷、更高效的要领。
这就是 javax.script 发生的原因了。Java Scripting API 是从 Java 6 开始引入的,它填补了便捷的小剧本语言和结实的 Java 生态系统之间的鸿沟。通过利用 Java Scripting API,您就可以在您的 Java 代码中快速整合险些所有的剧本语言,这使您可以或许在办理一些很小的问题时有更多可选择的要领。
1. 利用 jrunscript 执行 JavaScript
每一个新的 Java 平台宣布城市带来新的呼吁行东西集,它们位于 JDK 的 bin 目次。Java 6 也一样,个中 jrunscript 即是 Java 平台东西会合的一个不小的增补。
设想一个编写呼吁行剧本举办机能监控的简朴问题。这个东西将借用 jmap,每 5 秒钟运行一个 Java 历程,从而相识历程的运行状况。一般环境下,我们会利用呼吁行 shell 脚原来完成这样的事情,可是这里的处事器应用措施陈设在一些不同很大的平台上,包罗 Windows® 和 Linux®。系统打点员将会发明编写可以或许同时运行在两个平台的 shell 剧本是很疾苦的。凡是的做法是编写一个 Windows 批处理惩罚文件和一个 UNIX® shell 剧本,同时担保这两个文件同步更新。
可是,任何阅读过 The Pragmatic Programmer 的人都知道,这严重违反了 DRY (Don’t Repeat Yourself) 原则,并且会发生很多缺陷和问题。我们真正但愿的是编写一种与操纵系统无关的剧本,它可以或许在所有的平台上运行。
虽然,Java 语言是平台无关的,可是这里并不是需要利用 “系统” 语言的环境。我们需要的是一种剧本语言 — 如,JavaScript。
清单 1 显示的是我们所需要的简朴 shell 剧本:
清单 1. periodic.js
while (true)
{
echo("Hello, world!");
}
由于常常与 Web 欣赏器打交道,很多 Java 开拓人员已经知道了 JavaScript(或 ECMAScript;JavaScript 是由 Netscape 开拓的一种 ECMAScript 语言)。问题是,系统打点员要如何运行这个剧本?
虽然,办理要领是 JDK 所带的 jrunscript 实用措施,如清单 2 所示:
清单 2. jrunscript
C:\developerWorks\5things-scripting\code\jssrc>jrunscript periodic.js
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
...
留意,您也可以利用 for 轮回凭据指定的次数来轮回执行这个剧本,然后才退出。根基上,jrunscript 可以或许让您执行 JavaScript 的所有操纵。惟一差异的是它的运行情况不是欣赏器,所以运行中不会有 DOM。因此,最顶层的函数和工具稍微有些差异。
因为 Java 6 将 Rhino ECMAScript 引擎作为 JDK 的一部门,jrunscript 可以执行任何通报给它的 ECMAScript 代码,不管是一个文件(如此地方示)或是在越发交互式的 REPL(“Read-Evaluate-Print-Loop”)shell 情况。运行 jrunscript 就可以会见 REPL shell。
2. 从剧本会见 Java 工具
可以或许编写 JavaScript/ECMAScript 代码长短常好的,可是我们不但愿被迫从头编译我们在 Java 语言中利用的所有代码 — 这是违背我们初志的。幸好,所有利用 Java Scripting API 引擎的代码都完全可以或许会见整个 Java 生态系统,因为本质上一切代码都照旧 Java 字节码。所以,回到我们之前的问题,我们可以在 Java 平台上利用传统的 Runtime.exec() 挪用来启动历程,如清单 3 所示:
清单 3. Runtime.exec() 启动 jmap
var p = java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ])
p.waitFor()
数组 arguments 是指向通报到这个函数参数的 ECMAScript 尺度内置引用。在最顶层的剧本情况中,则是通报给剧本自己的的参数数组(呼吁行参数)。所以,在清单 3 中,这个剧本预期吸收一个参数,该参数包括要映射的 Java 历程的 VMID。
除此之外,我们可以操作自己为一个 Java 类的 jmap,然后直接挪用它的 main() 要领,如清单 4 所示。有了这个要领,我们不需要 “传输” Process 工具的 in/out/err 流。
清单 4. JMap.main()
var args = [ "-histo", arguments[0] ]
Packages.sun.tools.jmap.JMap.main(args)
Packages 语法是一个 Rhino ECMAScript 标识,它指向已经 Rhino 内建设的位于焦点 java.* 包之外的 Java 包。
#p#副标题#e#
3. 从 Java 代码挪用剧本
从剧本挪用 Java 工具仅仅完成了一半的事情:Java 剧本情况也提供了从 Java 代码挪用剧本的成果。这只需要实例化一个 ScriptEngine 工具,然后加载和评估剧本,如清单 5 所示:
清单 5. Java 平台的剧本挪用
#p#分页标题#e#
import java.io.*;
import javax.script.*;
public class App
{
public static void main(String[] args)
{
try
{
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
for (String arg : args)
{
FileReader fr = new FileReader(arg);
engine.eval(fr);
}
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
catch(ScriptException scrEx)
{
scrEx.printStackTrace();
}
}
}
eval() 要领也可以直接操纵一个 String,所以这个剧本不必然必需是文件系统的一个文件 — 它可以来自于数据库、用户输入,可能甚至可以基于情况和用户操纵在应用措施中生成。
4. 将 Java 工具绑定到剧本空间
仅仅挪用一个剧本还不足:剧本凡是会与 Java 情况中建设的工具举办交互。这时,Java 主机情况必需建设一些工具并将它们绑定,这样剧本就可以很容易找到和利用这些工具。这个进程是 ScriptContext 工具的任务,如清单 6 所示:
清单 6. 为剧本绑定工具
import java.io.*;
import javax.script.*;
public class App
{
public static void main(String[] args)
{
try
{
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
for (String arg : args)
{
Bindings bindings = new SimpleBindings();
bindings.put("author", new Person("Ted", "Neward", 39));
bindings.put("title", "5 Things You Didn't Know");
FileReader fr = new FileReader(arg);
engine.eval(fr, bindings);
}
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
catch(ScriptException scrEx)
{
scrEx.printStackTrace();
}
}
}
会见所绑定的工具很简朴 — 所绑定工具的名称是作为全局定名空间引入到剧本的,所以在 Rhino 中利用 Person 很简朴,如清单 7 所示:
清单 7. 是谁撰写了本文?
println("Hello from inside scripting!")
println("author.firstName = " + author.firstName)
您可以看到,JavaBeans 样式的属性被简化为利用名称直接会见,这就仿佛它们是字段一样。
5. 编译频繁利用的剧本
剧本语言的缺点一直存在于机能方面。个中的原因是,大大都环境下剧本语言是 “即时” 解译的,因而它在执行时会损失一些理会和验证文本的时间和 CPU 周期。运行在 JVM 的很多剧本语言最终会将吸收的代码转换为 Java 字节码,至少在剧本被第一次理会和验证时举办转换;在 Java 措施封锁时,这些即时编译的代码会消失。将频繁利用的剧本保持为字节码形式可以辅佐晋升可观的机能。
我们可以以一种很自然和有意义的要领利用 Java Scripting API。假如返回的 ScriptEngine 实现了 Compilable 接口,那么这个接口所编译的要领可用于将剧本(以一个 String 或一个 Reader 通报过来的)编译为一个 CompiledScript 实例,然后它可用于在 eval() 要领中利用差异的绑定反复地处理惩罚编译后的代码,如清单 8 所示:
清单 8. 编译解译后的代码
import java.io.*;
import javax.script.*;
public class App
{
public static void main(String[] args)
{
try
{
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
for (String arg : args)
{
Bindings bindings = new SimpleBindings();
bindings.put("author", new Person("Ted", "Neward", 39));
bindings.put("title", "5 Things You Didn't Know");
FileReader fr = new FileReader(arg);
if (engine instanceof Compilable)
{
System.out.println("Compiling....");
Compilable compEngine = (Compilable)engine;
CompiledScript cs = compEngine.compile(fr);
cs.eval(bindings);
}
else
engine.eval(fr, bindings);
}
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
catch(ScriptException scrEx)
{
scrEx.printStackTrace();
}
}
}
#p#分页标题#e#
在大大都环境中,CompiledScript 实例需要存储在一个长时间存储中(譬喻,servlet-context),这样才气制止一次次地反复编译沟通的剧本。然而,假如剧本产生变革,您就需要建设一个新的 CompiledScript 来反应这个变革;一旦编译完成,CompiledScript 就不再执行原始的剧本文件内容。
竣事语
Java Scripting API 在扩展 Java 措施的范畴和成果方眼前进了很大一步,而且它将剧本语言的编码效率的优势带到 Java 情况。jrunscript — 它显然不是很难编写的措施 — 以及 javax.script 给 Java 开拓人员带来了诸如 Ruby (JRuby) 和 ECMAScript (Rhino) 等剧本语言的优势,同时还不会粉碎 Java 情况的生态系统和可扩展性。