Java 脚本编程教程:集成 Groovy 和 JS(四)
十二、Nashorn 的 Java APIs
在本章中,您将学习:
- Nashorn 的 Java APIs 是什么
- 如何直接实例化 Nashorn 引擎
- 如何在 Java 代码中和命令行上将选项传递给 Nashorn 引擎
- 如何在 Nashorn 引擎中的脚本上下文之间共享全局
- 如何在 Java 代码中添加、更新、删除和读取脚本对象的属性
- 如何在 Java 代码中创建脚本对象并调用它们的方法
- 如何从 Java 代码中调用脚本函数
- 如何将脚本日期转换成 Java 日期
Nashorn 的 Java APIs 是什么?
在 Nashorn 脚本中使用 Java 类很简单。有时您可能想在 Java 代码中使用 Nashorn 对象。您可以将 Nashorn 对象传递给 Java 代码,或者 Java 代码可以评估 Nashorn 脚本并检索 Nashorn 对象的引用。当 Nashorn 对象跨越边界(脚本到 Java)时,它们需要表示为 Java 类的对象,并且您应该能够像使用任何其他 Java 对象一样使用它们。
如果您的应用只使用 Nashorn,为了充分利用 Nashorn 引擎,您可能希望使用 Nashorn 中可用的选项和扩展。您需要用 Java 代码实例化 Nashorn 引擎,使用特定于 Nashorn 引擎的类,而不是使用 Java 脚本 API 中的类。
Nashorn 的 Java APIs 提供了 Java 类和接口,允许您直接在 Java 代码中处理 Nashorn 脚本引擎和 Nashorn 对象。图 12-1 描述了当你处理 Nashorn 引擎时,你应该在客户端代码中使用的那些类和接口的类图。它们在jdk.nashorn.api.scripting包里。
图 12-1。
A class diagram of Java APIs for Nashorn in the jdk.nashorn.api.scripting package
注意,Nashorn 脚本引擎在内部使用了其他包中的许多其他类。然而,你不应该在你的应用中直接使用它们,除了来自jdk.nashorn.api.scripting包的类。位于wiki . open JDK . Java . net/display/Nashorn/Nashorn+JSR 223+engine+notes的网页包含了jdk.nashorn.api.scripting包文档的链接。
Note
JDK8u40 中已经增加了jdk.nashorn.api.scripting包中的ClassFilter接口,计划 2015 年一季度末出货。如果您想更早地使用这个接口,您需要下载 JDK8u40 的早期访问版本。
我将在后续章节中详细讨论一些特定于 Nashorn 的 Java 类和接口。表 12-1 列出了jdk.nashorn.api.scripting包中的类和接口及其描述。
表 12-1。
The List of Java Classes/Interfaces to be Used with Nashorn Scripting Engine
| 类别/接口 | 描述 |
|---|---|
NashornScriptEngineFactory | 这是 Nashorn 引擎的脚本引擎工厂实现。当您想要创建一个使用 Nashorn 特定选项和扩展的 Nashorn 引擎时,您需要实例化这个类。 |
NashornScriptEngine | 这是 Nashorn 引擎的脚本引擎实现类。不要直接实例化这个类。它的实例是使用一个NashornScriptEngineFactory对象的getScriptEngine()方法获得的。 |
NashornException | 这是从 Nashorn 脚本抛出的所有异常的基本异常类。当您使用 Java 脚本 API 时,您的 Java 代码接收到一个ScriptException类的实例,该实例将成为NashornException的包装器。如果您直接从 Java 代码中访问脚本,例如通过在脚本中实现 Java 接口并在 Java 代码中使用该接口的实例,则可能会在 Java 代码中直接抛出NashornException的实例。该类包含许多方法,这些方法使您可以访问脚本错误的详细信息,如行号、列号、脚本的错误对象等。 |
ClassFilter | ClassFilter是一个接口。您可以使用它的实例来限制 Nashorn 脚本中部分或全部 Java 类的可用性。当使用一个NashornScriptEngineFactory实例化一个NashornScriptEngine时,您将需要传递这个接口的一个实例。 |
ScriptUtils | 一个用于 Nashorn 脚本的实用程序类。 |
URLReader | 读取 URL 的内容。它继承自java.io.Reader类。 |
JSObject | 这个接口的一个实例用 Java 代码表示一个 Nashorn 对象。如果您想将一个 Java 对象传递给一个应该被视为 Nashorn 对象的 Nashorn 脚本,您需要传递这个接口的一个实例。您可以在 Nashorn 脚本中使用括号符号来访问和设置此类 Java 对象的属性。 |
AbstractJSObject | 这是一个实现了JSObject接口的抽象类。 |
ScriptObjectMirror | 这是一个镜像对象,它包装了一个 Nashorn 脚本对象。它继承自AbstractJSObject类并实现了Bindings接口。当 Java 代码从脚本中接收到一个 Nashorn 对象时,脚本对象在被传递给 Java 代码之前被包装在一个ScriptObjectMirror实例中。 |
实例化 Nashorn 引擎
在前面的章节中,您已经使用标准的 Java 脚本 API 用 Java 代码实例化了 Nashorn 引擎。Nashorn 引擎提供了几个自定义功能。为了利用这些特性,您需要直接实例化 Nashorn 引擎。首先,您需要创建一个NashornScriptEngineFactory类的对象,然后调用getScriptEngine()方法的一个重载版本来创建一个 Nashorn 引擎:
// Create a Nashorn engine factory
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
// Create a Nashorn engine with default options
ScriptEngine engine = factory.getScriptEngine();
默认情况下,Nashorn 引擎工厂创建一个启用了--dump-on-error选项的 Nashorn 引擎。在下一个示例中,我将向您展示如何为 Nashorn 引擎设置其他选项。
以下代码片段使用--no-java和–strict选项创建了一个 Nashorn 引擎:
// Create a Nashorn engine factory
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
// Store the Nashorn options in a String array
String[] options = {"--no-java", "-strict"};
// Create the Nashorn engine with the options
ScriptEngine engine = factory.getScriptEngine(options);
由于使用了--no-java选项,您不能在这个引擎执行的脚本中使用任何 Java 类。–strict选项将强制引擎以严格模式执行所有脚本。
您还可以在命令行上使用nashorn.args系统属性将选项传递给 Nashorn 引擎。以下命令运行com.jdojo.script.Test类,将四个选项传递给 Nashorn 引擎:
java -Dnashorn.args="--global-per-engine -strict --no-java --language=es5" com.jdojo.script.Test
请注意,选项由空格分隔,它们都作为一个字符串传递。如果只有一个要传递的选项,可以省略选项值两边的双引号。以下命令只将一个选项--no-java传递给引擎:
java -Dnashorn.args=--no-java com.jdojo.script.Test
下面的代码片段使用类过滤器创建了一个 Nashorn 引擎,它是ClassFilter接口的一个实例,用来限制来自com.jdojo包及其子包的任何类的使用。注意 JDK8u40 中增加了ClassFilter接口;它在 JDK8 中不可用:
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine(clsName -> clsName.startsWith("com.jdojo"));
ClassFilter是功能界面。它的方法被传递了 Nashorn 脚本试图使用的 Java 类的完全限定名。如果方法返回true,则可以在脚本中使用该类;否则,该类不能在脚本中使用。
Tip
如果不希望脚本中暴露任何 Java 类,可以使用clsName -> false作为ClassFilter的 lambda 表达式。在脚本中使用受ClassFilter限制的 Java 类会抛出java.lang.ClassNotFound异常。
下面是NashornScriptEngineFactory类的getScriptEngine()方法的重载版本列表:
ScriptEngine getScriptEngine()ScriptEngine getScriptEngine(String... args)ScriptEngine getScriptEngine(ClassFilter classFilter)ScriptEngine getScriptEngine(ClassLoader appLoader)ScriptEngine getScriptEngine(String[] args, ClassLoader appLoader)ScriptEngine getScriptEngine(String[] args, ClassLoader appLoader, ClassFilter classFilter)
共享引擎全局
默认情况下,Nashorn 引擎维护每个脚本上下文的全局对象。在本讨论中,术语“全局”指的是存储在 Nashorn 脚本的全局作用域中的全局变量和声明,您在顶级脚本中通过this引用它们。不要将脚本上下文中的全局范围绑定与脚本中的全局范围绑定混淆。当您在脚本中引用任何变量或创建变量时,会首先搜索脚本全局变量。如果您在脚本中引用了一个在脚本全局中找不到的变量,引擎将在脚本上下文的全局范围bindings中搜索它。例如,脚本对象Object、Math、String等等都是脚本全局的一部分。考虑清单 12-1 中的程序及其输出。
清单 12-1。每个 Nashron 引擎使用多个脚本全局变量
// MultiGlobals.java
package com.jdojo.script;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
public class MultiGlobals {
public static void main(String[] args) {
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Add a variable named msg to the script globals
engine.eval("var msg = 'Hello globals'");
// Print the value of the msg variable
engine.eval("print(this.msg);");
// Execute the same script as above, but using a new
// ScriptContext object. The engine will use a fresh
// copy of the globals and will not find this.msg that
// was created and associated with the default script
// context of the engine previously.
ScriptContext ctx = new SimpleScriptContext();
engine.eval("print(this.msg);", ctx);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Hello globals
undefined
该程序执行以下步骤:
- 使用默认选项创建 Nashorn 引擎。
- 使用使用引擎默认脚本上下文的
eval()方法执行脚本。该脚本创建了一个名为msg的全局变量,它存储在脚本 globals 中。你可以在全局范围内使用简单的名字msg或this.msg来引用变量。 - 使用
eval()方法执行脚本,该方法使用打印msg变量的值的引擎的默认脚本上下文。输出中的第一行确认打印出了msg变量的正确值。 - 使用使用新脚本上下文的
eval()方法执行脚本。该脚本试图打印全局变量msg的值。输出中的第二行,通过打印undefined,确认名为msg的变量在脚本 globals 中不存在。
这是 Nashorn 引擎的默认行为。它为每个脚本上下文创建全局变量的新副本。如果您想要使用默认上下文的全局变量(如果您想要共享全局变量),您可以通过将默认上下文的引擎作用域Bindings复制到您的新脚本上下文来实现。清单 12-2 使用这种方法在两个脚本上下文之间共享脚本全局变量。
清单 12-2。通过复制引擎默认上下文的引擎范围绑定来共享脚本全局变量
// CopyingGlobals.java
package com.jdojo.script;
import javax.script.Bindings;
import javax.script.ScriptContext;
import static javax.script.ScriptContext.ENGINE_SCOPE;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
public class CopyingGlobals {
public static void main(String[] args) {
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Add a variable named msg to the global scope of // the script
engine.eval("var msg = 'Hello globals'");
// Print the value of the msg value
engine.eval("print(this.msg);");
// Create a ScriptContext and copy the ENGINE_SCOPE // Bindings of the default
// script context to the new ScriptContext
ScriptContext ctx = new SimpleScriptContext();
ScriptContext defaultCtx = engine.getContext();
Bindings engineBindings = defaultCtx.getBindings(ENGINE_SCOPE);
ctx.setBindings(engineBindings, ENGINE_SCOPE);
// Use the new ScriptContext to execute the script
engine.eval("print(this.msg);", ctx);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Hello globals
Hello globals
Nashorn 引擎的–-global-per-engine选项完成了与前一个例子相同的事情。它在所有脚本上下文中共享脚本全局变量。清单 12-3 显示了如何为引擎设置这个选项。输出确认引擎仅使用一个全局副本来执行所有脚本。
清单 12-3。在 Nashorn 引擎中的所有脚本上下文之间共享脚本全局
// SharedGlobals.java
package com.jdojo.script;
import javax.script.Bindings;
import javax.script.ScriptContext;
import static javax.script.ScriptContext.ENGINE_SCOPE;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
public class SharedGlobals {
public static void main(String[] args) {
// Get the Nashorn script engine using the // --global-per_engine option
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine("--global-per-engine");
try {
// Add a variable named msg to the global scope of // the script
engine.eval("var msg = 'Hello globals'");
// Print the value of the msg value
engine.eval("print(this.msg);");
// Execute the same script, but using a new
// ScriptContext. Note that the script globals // are shared and this script will find the
// this.msg variable created by the first // script execution.
ScriptContext ctx = new SimpleScriptContext();
engine.eval("print(this.msg);", ctx);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Hello globals
Hello globals
在 Java 代码中使用脚本对象
当来自 Nashorn 的对象和值跨越 script-Java 边界进入 Java 代码时,它们需要被表示为 Java 对象和值。表 12-2 列出了脚本对象和它们对应的 Java 对象之间的类映射。
表 12-2。
The List of Mapping Between Classes of Script Objects and Java Objects
| 脚本对象的类型 | Java 对象的类型 |
|---|---|
| 不明确的 | jdk.nashorn.internal.runtime.Undefined |
| 空 | null |
| 数字 | java.lang.Number |
| 布尔代数学体系的 | java.lang.Boolean |
| 原始字符串类型 | java.lang.String |
| 任何脚本对象 | jdk.nashorn.api.scripting.ScriptObjectMirror |
| 任何 Java 对象 | The same as the Java object in the script |
注意,您不应该使用jdk.nashorn.internal包及其子包中的类和接口。表中列出它们只是为了提供信息。在此表中,Nashorn 数字类型的映射显示为java.lang.Number类型。尽管 Nashorn 试图尽可能地为数值传递java.lang.Integer和 j ava.lang.Double对象,但是依赖这样的专用 Java 类型是不可靠的。当一个 Nashorn 数从脚本传递到 Java 代码时,您应该会看到一个java.lang.Number实例。只要有必要,Nashorn 中的 Number 和 Boolean 类型的值都会被转换成相应的 Java 原语类型,比如int、double、boolean等等。
在 Nashorn 中,脚本对象被表示为jdk.nashorn.internal.runtime.ScriptObject类的一个实例。当脚本对象被传递给 Java 代码时,它被包装在一个ScriptObjectMirror对象中。在 JDK8u40 之前,如果你将一个脚本对象传递给一个声明其参数为java.lang.Object类型的 Java 方法,传递的是一个ScriptObject,而不是一个ScriptObjectMirror。JDK8u40 改变了这种行为,每次都将一个脚本对象作为ScriptObjectMirror传递给 Java 代码。如果您在 Java 中将方法的参数声明为JSObject或ScriptObjectMirror,Nashorn 总是将脚本对象作为ScriptObjectMirror传递给 Java。
Tip
脚本可以将值undefined传递给 Java 代码。您可以使用ScriptObjectMirror类的isUndefined(Object obj)静态方法来检查传递给 Java 的脚本对象是否是undefined。如果指定的obj为undefined,则该方法返回 true 否则,它返回 false。
ScriptObjectMirror类实现了JSObject和Bindings接口。该类添加了更多的方法来处理脚本对象。您可以将 Java 代码中脚本对象的引用存储在三种类型的变量中的任何一种:JSObject、Bindings或ScriptObjectMirror。你用什么类型取决于你的需要。使用ScriptObjectMirror类型给你更多的灵活性和访问脚本对象的所有特性。表 12-3 包含了在JSObject接口中声明的方法列表。
我将在接下来的小节中展示如何使用这些方法和ScriptObjectMirror类的一些方法。任何 Java 类都可以实现JSObject接口。这种类的对象可以像脚本对象一样使用,并且可以使用语法obj.func()、obj[prop]、delete obj.prop等在脚本中处理它们的方法和属性。
表 12-3。
The List of Methods Declared in the JSObject Interface with Their Descriptions
| 方法 | 描述 |
|---|---|
Object call(Object thiz, Object... args) | 将此对象作为函数调用。当JSObject包含 Nashorn 函数或方法的引用时,您将使用该方法。参数thiz被用作函数调用中this的值。args中的参数作为参数传递给被调用的函数。这个方法在 Java 中的工作方式与func.apply(thiz, args)在 Nashorn 脚本中的工作方式相同。 |
Object eval(String script) | 评估指定的脚本。 |
String getClassName() | 返回对象的 ECMAScript 类名。这和 Java 里的类名不一样。 |
Object getMember(String name) | 返回脚本对象的命名属性的值。 |
Object getSlot(int index) | 返回脚本对象的索引属性的值。 |
boolean hasMember(String name) | 如果脚本对象具有命名属性,则返回 true 否则返回 false。 |
boolean hasSlot(int index) | 如果脚本对象具有索引属性,则返回 true 否则返回 false。 |
boolean isArray() | 如果脚本对象是数组对象,则返回 true 否则返回 false。 |
boolean isFunction() | 如果脚本对象是函数对象,则返回 true 否则返回 false。 |
boolean isInstance(Object instance) | 如果指定的instance是该对象的实例,则返回 true 否则返回 false。 |
boolean isInstanceOf(Object clazz) | 如果该对象是指定的clazz的实例,则返回 true 否则返回 false。 |
boolean isStrictFunction() | 如果该对象是严格函数,则返回 true 否则返回 false。 |
Set<String> keySet() | 以一组字符串的形式返回该对象的所有属性的名称。 |
Object newObject(Object... args) | 调用此方法的对象应该是构造函数对象。调用此构造函数创建一个新对象。这相当于 Nashorn 脚本中的new func(arg1, arg2...)。 |
void removeMember(String name) | 从对象中移除指定的属性。 |
void setMember(String name, Object value) | 将指定的value设置为该对象的指定属性名。如果属性名不存在,则添加一个具有指定name的新属性。 |
void setSlot(int index, Object value) | 将指定的value设置为该对象的指定索引属性。如果index不存在,则添加一个具有指定index的新属性。 |
double toNumber() | 返回对象的数值。通常,如果脚本对象是数值的包装器,比如在 Nashorn 中使用表达式new Object(234.90)创建的脚本对象,您将使用此方法。在其他脚本对象上,它返回值Double.NAN。 |
Collection<Object> values() | 在一个Collection中返回该对象的所有属性值。 |
使用脚本对象的属性
ScriptObjectMirror类实现了JSObject和Bindings接口。您可以使用这两个接口的方法来访问属性。您可以使用JSObject的getMember()和getSlot()方法来读取脚本对象的命名和索引属性。您可以使用Bindings的get()方法来获取属性的值。您可以使用JSObject的setMember()和setSlot()方法来添加和更新属性。Bindings的put()方法可以让你做同样的事情。
您可以使用JSObject的hasMember()和hasSlot()方法来检查命名属性和索引属性是否存在。同时,您可以使用Bindings接口的containsKey()方法来检查一个属性是否存在。您可以认为由ScriptObjectMirror类实现的JSObject和Bindings接口提供了同一个脚本对象的两个视图——前者是一个简单的对象,后者是一个地图。
清单 12-4 包含一个 Nashorn 脚本,它创建两个脚本对象并调用一个 Java 类的两个方法,如清单 12-5 所示。该脚本将脚本对象传递给 Java 方法。Java 方法打印传递的对象的属性,并向两个脚本对象添加一个新属性。该脚本在方法返回后打印属性,以确认 Java 代码中添加到脚本对象的新属性仍然存在。
清单 12-4。一个 Nashorn 脚本,它调用 Java 方法并将脚本对象传递给它们
// scriptobjectprop.js
// Create an object
var point = {x: 10, y: 20};
// Create an array
var empIds = [101, 102];
// Get the Java type
var ScriptObjectProperties = Java.type("com.jdojo.script.ScriptObjectProp");
// Pass the object to Java
ScriptObjectProperties.propTest(point);
// Print all properties of the point object
print("In script, after calling the Java method propTest()...");
for(var prop in point) {
var value = point[prop];
print(prop + " = " + value);
}
// Pass the array object to Java
ScriptObjectProperties.arrayTest(empIds);
// Print all elements of teh empIds array
print("In script, after calling the Java method arrayTest()...");
for(var i = 0, len = empIds.length; i < len; i++) {
var value = empIds[i];
print("empIds[" + i + "] = " + value);
}
清单 12-5。一个 Java 类,其方法访问脚本对象的属性
// ScriptObjectProp.java
package com.jdojo.script;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
public class ScriptObjectProp {
public static void main(String[] args) {
// Construct the script file path
String scriptFileName = "scriptobjectprop.js";
Path scriptPath = Paths.get(scriptFileName);
// Make sure the script file exists. If not, print the full // path of the script file and terminate the program.
if (!Files.exists(scriptPath)) {
System.out.println(scriptPath.toAbsolutePath()
+ " does not exist.");
return;
}
// Create a scripting engine manager
ScriptEngineManager manager = new ScriptEngineManager();
// Obtain a Nashorn scripting engine from the manager
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Execute the script that will call the propTest() and
// arrayTest() methods of this class
engine.eval("load('" + scriptFileName + "')");
}
catch (ScriptException e) {
e.printStackTrace();
}
}
public static void propTest(ScriptObjectMirror point) {
// List all properties
System.out.println("Properties of point received in Java...");
for (String prop : point.keySet()) {
Object value = point.getMember(prop);
System.out.println(prop + " = " + value);
}
// Let us add a property named z
System.out.println("Adding z = 30 to point in Java... ");
point.setMember("z", 30);
}
public static void arrayTest(ScriptObjectMirror empIds) {
if (!empIds.isArray()) {
System.out.println("Passed in obejct is not an array.");
return;
}
// Get the length proeprty of teh array
int length = ((Number) empIds.getMember("length")).intValue();
System.out.println("empIds received in Java...");
for (int i = 0; i < length; i++) {
int value = ((Number) empIds.getSlot(i)).intValue();
System.out.printf("empIds[%d] = %d%n", i, value);
}
// Let us add an element to the array
System.out.println("Adding empIds[2] = 103 in Java... ");
empIds.setSlot(length, 103);
}
}
Properties of point received in Java...
x = 10
y = 20
Adding z = 30 to point in Java...
In script, after calling the Java method propTest()...
x = 10
y = 20
z = 30
empIds received in Java...
empIds[0] = 101
empIds[1] = 102
Adding empIds[2] = 103 in Java...
In script, after calling the Java method arrayTest()...
empIds[0] = 101
empIds[1] = 102
empIds[2] = 103
在 Java 中创建 Nashorn 对象
您可能在脚本中有构造函数,并希望使用这些构造函数在 Java 中创建对象。ScriptObjectMirror类的newObject()方法允许您在 Java 中创建脚本对象。方法声明是:
Object newObject(Object... args)
方法的参数是要传递给构造函数的参数。方法返回新的对象引用。此方法需要在构造函数上调用。首先,您需要获取 Java 中构造函数的引用作为一个ScriptObjectMirror对象,然后调用这个方法使用这个构造函数创建一个新的脚本对象。
您可以使用ScriptObjectMirror类的callMember()方法调用脚本对象的方法。该方法的声明是:
Object callMember(String methodName, Object... args)
在第四章中,你创建了一个名为Point的构造函数。它的声明如清单 12-6 所示,供您参考。在这个例子中,您将在 Java 中创建Point类型的对象。
清单 12-6。Nashorn 中名为 Point 的构造函数的声明
// Point.js
// Define the Point constructor
function Point(x, y) {
this.x = x;
this.y = y;
}
// Override the toString() method in Object.prototype
Point.prototype.toString = function() {
return "Point(" + this.x + ", " + this.y + ")";
};
// Define a new method called distance()
Point.prototype.distance = function(otherPoint) {
var dx = this.x - otherPoint.x;
var dy = this.y - otherPoint.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist;
};
清单 12-7 包含了执行下列步骤的 Java 程序:
- 加载将加载
Point构造函数定义的point.js脚本文件 - 通过调用
engine.get("Point")获取Point构造函数的引用 - 通过调用
Point构造函数上的newObject()方法创建两个名为p1和p2的Point对象 - 调用
p1上的callMember()方法来调用它的distance()方法来计算p1和p2之间的距离 - 调用
p1和p2上的callMember()方法,通过调用在Point构造函数中声明的toString()方法来获取它们的字符串表示 - 最后,打印两点之间的距离
清单 12-7。用 Java 代码创建 Nashorn 对象
// CreateScriptObject.java
package com.jdojo.script;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
public class CreateScriptObject {
public static void main(String[] args) {
// Construct the script file path
String scriptFileName = "point.js";
Path scriptPath = Paths.get(scriptFileName);
// Make sure the script file exists. If not, print the full
// path of the script file and terminate the program.
if (! Files.exists(scriptPath) ) {
System.out.println(scriptPath.toAbsolutePath() +
" does not exist.");
return;
}
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Execute the script in the file
engine.eval("load('" + scriptFileName + "');");
// Get the Point constructor as a ScriptObjectMirror // object
ScriptObjectMirror pointFunc = (ScriptObjectMirror)engine.get("Point");
// Create two Point objects. The following statements // are the same as var p1 = new Point(10, 20);
// and var p2 = new Point(13, 24); in a Nashorn script
ScriptObjectMirror p1 = (ScriptObjectMirror)pointFunc.newObject(10, 20);
ScriptObjectMirror p2 = (ScriptObjectMirror)pointFunc.newObject(13, 24);
// Compute the distance between p1 and p2 calling
// the distance() method of the Point object
Object result = p1.callMember("distance", p2);
double dist = ((Number)result).doubleValue();
// Get the string forms of p1 and p2 by calling // their toString() method
String p1Str = (String)p1.callMember("toString");
String p2Str = (String)p2.callMember("toString");
System.out.printf("The distance between %s and %s is %.2f.%n",
p1Str, p2Str, dist);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
The distance between Point(10, 20) and Point(13, 24) is 5.00.
从 Java 调用脚本函数
您可以从 Java 代码中调用脚本函数。可以从脚本向 Java 代码传递脚本函数对象,或者它可以通过脚本引擎获得函数对象的引用。您可以在作为函数对象引用的ScriptObjectMirror上使用call()方法。该方法的声明是:
Object call(Object thiz, Object... args)
第一个参数是在函数调用中用作this的对象引用。第二个参数是传递给函数的参数列表。要在全局范围内调用函数,使用null作为第一个参数。
在第四章中,你创建了一个factorial()函数来计算一个数的阶乘。这个函数的声明如清单 12-8 所示。
清单 12-8。factorial()函数的声明
// factorial.js
// Returns true if n is an integer. Otherwise, returns false.
function isInteger(n) {
return typeof n === "number" && isFinite(n) && n%1 === 0;
}
// Define a function that computes and returns the factorial of an integer
function factorial(n) {
if (!isInteger(n)) {
throw new TypeError("The number must be an integer. Found:" + n);
}
if(n < 0) {
throw new RangeError("The number must be greater than 0\. Found: " + n);
}
var fact = 1;
for(var counter = n; counter > 1; fact *= counter--);
return fact;
}
清单 12-9 包含的 Java 程序从factorial.js文件加载脚本,在ScriptObjectMirror中获取factorial()函数的引用,使用call()方法调用factorial()函数,并打印结果。最后,程序使用ScriptObjectMirror类的call()方法调用String对象上的Array.prototype.join()函数。
清单 12-9。从 Java 代码调用脚本函数
// InvokeScriptFunctionInJava.java
package com.jdojo.script;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
public class InvokeScriptFunctionInJava {
public static void main(String[] args) {
// Construct the script file path
String scriptFileName = "factorial.js";
Path scriptPath = Paths.get(scriptFileName);
// Make sure the script file exists. If not, print the full
// path of the script file and terminate the program.
if (!Files.exists(scriptPath)) {
System.out.println(scriptPath.toAbsolutePath()
+ " does not exist.");
return;
}
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Execute the script in the file, so teh factorial() function is loaded
engine.eval("load('" + scriptFileName + "');");
// Get the reference of the factorial() script function
ScriptObjectMirror factorialFunc = (ScriptObjectMirror)engine.get("factorial");
// Invoke the factorial function and print the result
Object result = factorialFunc.call(null, 5);
double factorial = ((Number) result).doubleValue();
System.out.println("Factorial of 5 is " + factorial);
/* Call the Array.prototype.join() function on a String object */
// Get the reference of the Array.prototype.join method
ScriptObjectMirror arrayObject =
(ScriptObjectMirror)engine.eval("Array.prototype");
ScriptObjectMirror joinFunc =
(ScriptObjectMirror)arrayObject.getMember("join");
// Call the join() function of Array.prototype on a // string object passing a hyphen as a separator
String thisObject = "Hello";
String separator = "-";
String joinResult = (String)joinFunc.call(thisObject, separator);
System.out.println(joinResult);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Factorial of 5 is 120.0
H-e-l-l-o
ScriptObjectMirror类包含其他几个方法来处理 Java 代码中的脚本对象。您可以使用seal()、freeze()和preventExtensions()方法来密封、冻结和阻止脚本对象的扩展。它们的工作原理与 Nashorn 脚本中的Object.seal()、Object.freeze()和Object.preventExtensions()方法相同。该类包含一个名为to()的实用方法,声明如下:
<T> T to(Class<T> type)
您可以使用to()方法将脚本对象转换为指定的type。
将脚本日期转换为 Java 日期
你将如何在 Java 中创建一个日期对象,比如从一个脚本Date对象创建一个java.time.LocalDate对象?将脚本日期转换成 Java 日期并不简单。这两种日期有一个共同点:它们使用相同的纪元,即 UTC 1970 年 1 月 1 日午夜。您需要获取 JavaScript 日期中自纪元以来经过的毫秒数,创建一个Instant,并从Instant创建一个ZonedDateTime。清单 12-10 包含了一个完整的程序来演示 JavaScript 和 Java 之间的日期转换。运行这个程序时,您可能会得到不同的输出。在输出中,JavaScript 日期的字符串形式不包含毫秒部分,而 Java 对应部分包含毫秒部分。然而,这两个日期在内部表示同一时刻。
清单 12-10。将脚本日期转换为 Java 日期
// ScriptDateToJavaDate.java
package com.jdojo.script;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
public class ScriptDateToJavaDate {
public static void main(String[] args) {
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Create a Date object in script
ScriptObjectMirror jsDt = (ScriptObjectMirror)engine.eval("new Date()");
// Get the string representation of the script date
String jsDtString = (String) jsDt.callMember("toString");
System.out.println("JavaScript Date: " + jsDtString);
// Get the epoch milliseconds from the script date
long jsMillis = ((Number) jsDt.callMember("getTime")).longValue();
// Convert the milliseconds from JavaScript date to // java Instant
Instant instant = Instant.ofEpochMilli(jsMillis);
// Get a ZonedDateTime for the system default zone id
ZonedDateTime zdt =
ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println("Java ZonedDateTime: " + zdt);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
JavaScript Date: Fri Oct 31 2014 18:17:02 GMT-0500 (CDT)
Java ZonedDateTime: 2014-10-31T18:17:02.489-05:00[America/Chicago]
摘要
Nashorn 的 Java APIs 提供了 Java 类和接口,允许您直接在 Java 代码中处理 Nashorn 脚本引擎和 Nashorn 对象。Nashorn 的所有 Java APIs 类都在jdk.nashorn.api.scripting包中。注意,Nashorn 脚本引擎在内部使用了其他包中的许多其他类。然而,您不应该在您的应用中直接使用它们,除了来自jdk.nashorn.api.scripting包的类。
Nashorn 脚本可以使用不同类型的值和对象,例如数字、字符串、原始值的布尔类型以及对象、字符串、日期和自定义对象。您也可以在脚本中创建 Java 对象。Number 和 Boolean 类型的脚本值在 Java 中表示为java.lang.Number和java.lang.Boolean。脚本中的空类型在 Java 中表示为null。脚本中的 String 原语类型在 Java 中表示为java.lang.String。在 Java 中,脚本对象被表示为ScriptObjectMirror类的对象。
jdk.nashorn.api.scripting包中的NashornScriptEngineFactory和ScriptEngine类分别代表 Nashorn 脚本引擎工厂和脚本引擎。如果您想将选项传递给 Nashorn 引擎,您将需要直接使用这些类。您还可以在命令行上使用-Dnashorn.args="<options>"将选项传递给 Nashorn 引擎。如果您将--globals-per-engine传递给 Nashorn 引擎,所有脚本上下文将共享全局变量。默认情况下,脚本上下文不共享全局变量。
ScriptObjectMirror类包含几个从脚本对象添加、更新和删除属性的方法。该类实现了JSObject和Bindings接口。JSObject接口让您将脚本对象视为简单的 Java 类,而Bindings接口让您将脚本对象视为地图。您可以使用getMember()、getSlot()和get()方法来获取脚本对象的属性值。您可以使用setMember()、setSlot()和put()方法来添加或更新属性值。您可以使用callMember()方法来调用脚本对象的方法。call()方法允许您调用脚本函数,允许您为函数调用传递this值。ScriptObjectMirror类包含其他几个方法,让您在 Java 程序中使用脚本对象。
十三、调试、跟踪和分析脚本
在本章中,您将学习:
- 如何在 NetBeans IDE 中调试独立的 Nashorn 脚本
- 如何在 NetBeans IDE 中调试从 Java 代码调用的 Nashorn 脚本
- 如何跟踪和分析 Nashorn 脚本
带有 JDK 8 或更高版本的 NetBeans 8 支持调试、跟踪和分析 Nashorn 脚本。您可以在 NetBeans IDE 中运行和调试独立的 Nashorn 脚本。当从 Java 代码调用 Nashorn 脚本时,您也可以调试它们。您可以使用调试脚本的所有调试功能来调试 Java 代码;您可以设置断点、显示变量值、添加观察器、监视调用堆栈等等。调试 Nashorn 脚本时,调试器显示 Nashorn 堆栈。
在 NetBeans 中,所有与调试器相关的窗格都可以使用菜单项Windows ➤ Debugging打开。有关 NetBeans 中完整调试功能的列表,请参考 NetBeans 中的帮助页面。当 NetBeans 应用处于活动状态时,您可以通过按下F1键来打开帮助页面。在本章中,我将使用清单 13-1 中的脚本作为调试脚本的例子。
清单 13-1。包含 isPrime()函数和对该函数的调用的测试脚本
// primetest.js
function isPrime(n) {
// Integers <= 2, floating-point numbers, and even numbers are not // primes
if (n <= 2 || Math.floor(n) !== n || n % 2 === 0) {
return false;
}
// Check if n is divisible by any odd integers between 3 and sqrt(n).
var sqrt = Math.sqrt(n);
for (var i = 3; i <= sqrt; i += 2) {
if (n % i === 0) {
return false;
}
}
return true; // If we get here, it is a prime number.
}
// Check few nubmers for being primes
var num = 8;
var isPrimeNum = isPrime(num);
print(num + " is a prime number: " + isPrimeNum);
debugger;
num = 37;
isPrimeNum = isPrime(num);
print(num + " is a prime number: " + isPrimeNum);
调试独立脚本
要在 NetBeans 中运行或调试独立的 Nashorn 脚本,首先需要在 NetBeans IDE 中打开脚本文件。图 13-1 显示了在 NetBeans 中打开的清单 13-1 所示的脚本。要运行脚本,在显示脚本的编辑器中右击并选择Run File菜单项。或者,在脚本窗格激活时按下Shift + F6。
图 13-1。
A Nashorn script opened in the NetBeans IDE
要调试脚本,您需要使用以下三种方法之一添加断点:
- 将光标放在要设置/取消设置断点的行上。右键选择菜单项
Toggle Line Breakpoint,设置和取消设置断点。 - 将光标放在您想要设置/取消设置断点的行上,然后按
Ctrl + F8。第一次按下此组合键会设置断点。如果已经设置了断点,则按下相同的组合键会取消设置断点。 - 在脚本中添加
debugger语句。当调试会话处于活动状态时,debugger语句就像一个断点。否则没有效果。
图 13-2 显示了在第 21 行和第 26 行有两个断点的相同脚本。
图 13-2。
A Nashorn script opened in the NetBeans IDE that has two breakpoints
现在您已经准备好调试脚本了。在脚本窗格中右击并选择Debug File菜单项。或者,当脚本窗格激活时,按下Ctrl + Shift + F5。它将启动调试会话,如图 13-3 所示。调试器在第一个断点处停止,如图所示。在底部,您会看到Variables窗格打开,显示所有变量及其值。如果您想查看调试会话的其他细节,请使用主菜单Window ➤ Debugging下的一个菜单项。如果您关闭任何调试窗格,如Variables窗格,您可以使用Window ➤ Debugging菜单重新打开它们。
图 13-3。
A Nashorn script opened in the NetBeans IDE in an active debugging session
当调试器会话处于活动状态时,您可以使用调试操作,如单步执行、单步跳过、单步退出、继续等。这些操作可从名为Debug的主菜单项以及调试器工具栏中获得。图 13-4 显示了调试器工具栏。默认情况下,当调试器会话处于活动状态时,它会出现。如果调试器会话激活时不可见,右击工具栏区域并选择Debug菜单项使其可见。表 13-1 包含脚本可用的调试操作及其快捷方式和描述。
表 13-1。
The List of Debugger Actions in NetBeans
| 调试操作 | 捷径 | 描述 |
|---|---|---|
| 完成调试器 | Shift + F5 | 结束调试会话。 |
| 中止 | 停止当前会话中的所有线程。 | |
| 继续 | Ctrl + F5 | 继续执行程序,直到下一个断点。 |
| 跨过 | F8 | 执行当前行并将程序计数器移动到文件中的下一行。如果执行的行是对函数的调用,那么函数中的代码也会被执行。 |
| 跳过表达式 | Shift + F8 | 使您能够继续执行表达式中的每个方法调用,并查看每个方法调用的输入参数和结果输出值。如果没有进一步的方法调用,它的行为类似于单步执行操作。 |
| 进入 | F7 | 执行当前行。如果该行是对一个函数的调用,并且被调用的函数有可用的源代码,则程序计数器移到该函数的声明处。否则,程序计数器移动到脚本中的下一行。 |
| 走出去 | Ctrl + F7 | 执行当前函数中的其余代码,并将程序计数器移动到函数调用方之后的行。如果您单步执行了不再需要调试的函数,请使用此操作。 |
图 13-4。
The items in the debugger toolbar in NetBeans IDE
从 Java 代码调试脚本
从 Java 代码调用的调试脚本的工作方式略有不同。仅仅在脚本文件中设置断点并启动调试器或从 Java 代码进入脚本是不起作用的。您将使用清单 13-2 所示的 Java 程序来调试清单 13-1 所示的脚本。Java 程序使用一个Reader来执行来自primetest.js文件的脚本。但是,您也可以使用load()功能。在清单 13-2 中,您可以用下面的代码片段替换try-catch块中的代码,程序将会同样工作;您需要删除两个从java.io包中导入类的 import 语句:
try {
// Execute the script in the file
engine.eval("load('" + scriptFileName + "');"); // First time, add a // breakpoint here
}
catch (ScriptException e) {
e.printStackTrace();
}
清单 13-2。调试从 Java 代码调用的脚本
// PrimeTest.java
package com.jdojo.script;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class PrimeTest {
public static void main(String[] args) {
// Construct the script file path
String scriptFileName = "primetest.js";
Path scriptPath = Paths.get(scriptFileName);
// Make sure the script file exists. If not, print the full
// path of the script file and terminate the program.
if (!Files.exists(scriptPath)) {
System.out.println(scriptPath.toAbsolutePath() +" "does not exist.");
return;
}
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Get a Reader for the script file
Reader scriptReader = Files.newBufferedReader(scriptPath);
// Execute the script in the file
engine.eval(scriptReader); // First time, add a // breakpoint here
}
catch (IOException | ScriptException e) {
e.printStackTrace();
}
}
}
作为调试脚本的第一步,您需要在调用脚本引擎的eval()方法的代码行设置一个断点。如果不执行此步骤,您将无法从调试器单步执行脚本。图 13-5 显示了在第 35 行有一个断点的PrimeTest类的代码。
图 13-5。
The code of the PrimeTest class with a breakpint at line 35 in the NetBeans IDE
下一步是启动调试器。当包含PrimeTest类的编辑器窗格处于活动状态时,您可以使用Ctrl + Shift + F5。调试器将在第 35 行的断点处停止。你需要通过按F7进入eval()方法调用;这将把你带到AbstractScriptEngine.java文件,如图 13-6 所示。
图 13-6。
The debugger window when the AbstractScriptEngine.java file is being debugged
按下F7进入eval()方法调用。调试器将打开一个名为<eval>.js的文件,其中包含您试图使用Reader通过 Java 代码加载的primetest.js文件中的脚本。您可以滚动脚本并在<eval>.js文件中设置断点。图 13-7 显示了带有两个断点的文件——一个在第 21 行,一个在第 27 行。
图 13-7。
The debugger window showing the loaded script in the .js file
在<eval>.js文件的脚本中设置了断点之后,就可以继续正常的调试操作了。例如,Continue调试动作(F5)会在下一个断点停止执行。
当调试器结束时,您可以从 Java 代码中移除断点,在本例中,Java 代码是PrimaTest.java文件。如果您启动一个新的调试会话,调试器将在您之前在<eval>.js文件中设置的断点处停止。请注意,您只需进入<eval>.js文件一次。所有后续调试会话都会记住先前会话中的断点。
Tip
即使 Nashorn 支持debugger语句,NetBeans IDE 似乎也不会将其识别为 Nashorn 脚本中的断点。当调试器处于活动状态时,在脚本中添加debugger语句不会暂停执行。
跟踪和分析脚本
Nashorn 支持调用点跟踪和分析。您可以在jjs命令行工具以及嵌入式 Nashorn 引擎中启用这些选项。您可以为引擎运行的所有脚本或每个脚本/函数启用跟踪和分析。–tcs选项为所有脚本启用调用点跟踪,并在标准输出中打印调用点跟踪信息。-pcs选项为所有脚本启用调用点分析,并在当前目录下名为NashornProfile.txt的文件中打印调用点分析数据。
您可以在脚本或函数的开头使用以下四个 Nashorn 指令,有选择地跟踪和分析整个脚本或函数:
"nashorn callsite trace enterexit"; // Equivalent to -tcs=enterexit"nashorn callsite trace miss"; // Equivalent to -tcs=miss"nashorn callsite trace objects"; // Equivalent to -tcs=objects"nashorn callsite profile"; // Equivalent to -pcs
Tip
–tcs和–pcs选项基于每个脚本引擎工作,而四个跟踪和分析指令基于每个脚本和每个函数工作。
这些 Nashorn 指令仅在调试模式下启用。您可以通过将nashorn.debug系统属性设置为 true 来启用 Nashorn 调试模式。这些指令在 JDK8u40 和更高版本中可用。在撰写本书时,JDK8u40 仍在开发中。清单 13-3 显示了一个为函数启用了 Nashorn callsite 配置文件选项的脚本。该脚本已保存在名为primeprofiler.js的文件中。
清单 13-3。为函数启用了 Nashorn Callsite 配置文件指令的脚本
// primeprofiler.js
function isPrime(n) {
// Profile this function only
"nashorn callsite profile";
// Integers <= 2, floating-point numbers, and even numbers are not primes
if (n <= 2 || Math.floor(n) !== n || n % 2 === 0) {
return false;
}
// Check if n is divisible by any odd integers between 3 and sqrt(n).
var sqrt = Math.sqrt(n);
for (var i = 3; i <= sqrt; i += 2) {
if (n % i === 0) {
return false;
}
}
return true; // If we get here, it is a prime number.
}
// Check few nubmers for being primes
var num = 8;
var isPrimeNum = isPrime(num);
print(num + " is a prime number: " + isPrimeNum);
num = 37;
isPrimeNum = isPrime(num);
print(num + " is a prime number: " + isPrimeNum);
以下命令在启用 Nashorn 调试选项的情况下运行primeprofile.js文件中的脚本:
c:\>jjs -J-Dnashorn.debug=true primeprofile.js
8 is a prime number: false
37 is a prime number: true
C:\
该命令将在当前直接生成一个名为NashornProfile.txt的文件,该文件包含isPrime()函数调用的概要数据。这个文件的内容如清单 13-4 所示。
清单 13-4。NashornProfile.txt 文件的内容
0 dyn:getProp|getElem|getMethod:Math 438462 2
1 dyn:getMethod|getProp|getElem:floor 433936 2
2 dyn:call 650602 2
3 dyn:getProp|getElem|getMethod:Math 313834 1
4 dyn:getMethod|getProp|getElem:sqrt 283356 1
5 dyn:call 0 1
清单 13-5 包含设置nashorn.debug系统属性并运行清单 13-3 所示脚本的 Java 程序。运行该程序将在当前目录下创建一个NashornProfile.txt文件,该文件的内容与清单 13-2 所示的相同。
清单 13-5。设置 nashorn.debug 系统属性和分析脚本
// ProfilerTest.java
package com.jdojo.script;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ProfilerTest {
public static void main(String[] args) {
// Set the nashorn.debug system property, so the tracing and
// profiling directives will be recognized
System.setProperty("nashorn.debug", "true");
// Construct the script file path
String scriptFileName = "primeprofiler.js";
Path scriptPath = Paths.get(scriptFileName);
// Make sure the script file exists. If not, print the full
// path of the script file and terminate the program.
if (!Files.exists(scriptPath)) {
System.out.println(scriptPath.toAbsolutePath() + "does not exist.");
return;
}
// Get the Nashorn script engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
// Get a Reader for the script file
Reader scriptReader = Files.newBufferedReader(scriptPath);
// Execute the script in the file
engine.eval(scriptReader);
}
catch (IOException | ScriptException e) {
e.printStackTrace();
}
}
}
8 is a prime number: false
37 is a prime number: true
摘要
带有 JDK 8 或更高版本的 NetBeans 8 支持在 NetBeans IDE 中调试 Nashorn 脚本。您可以在 NetBeans IDE 中运行和调试独立的 Nashorn 脚本。当从 Java 代码调用 Nashorn 脚本时,您也可以调试它们。调试器无缝地从 Java 代码跳到 Nashorn 脚本。
Nashorn 支持调用点跟踪和分析。您可以在jjs命令行工具以及嵌入式 Nashorn 引擎中启用这些选项。您可以为引擎运行的所有脚本或每个脚本/函数启用跟踪和分析。–tcs选项为引擎运行的所有脚本启用调用点跟踪。-pcs选项为引擎运行的所有脚本启用调用点分析,并在当前目录中名为NashornProfile.txt的文件中打印调用点分析数据。JDK8u40 增加了四个 Nashorn 指令:"nashorn callsite trace enterexit"、"nashorn callsite trace miss"、"nashorn callsite trace objects"和"nashorn callsite profile"。这些指令可以添加到脚本和函数的开头,以便有选择地跟踪和分析脚本和函数。它们仅在调试模式下工作,可通过将系统属性nashorn.debug设置为 true 来启用。