Nashorn:如何在 Java 脚本执行之前在 Java 中预先设置 Java.type()-vars?

Nashorn: How to pre-set Java.type()-vars inside of Java before JavaScript execution?

我目前正在使用此 java 代码执行我的 JavaScript 脚本:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("awesome_script.js"));

我需要从 Java 脚本调用 Java 函数,所以我在 awesome_script.js 文件的顶部定义了它:

var first = Java.type('io.github.awesomeprogram.FirstClass');
var second = Java.type('io.github.awesomeprogram.SecondClass');
var extra = Java.type('io.github.awesomeprogram.ExtraClass');

然后我可以从这些 类 中调用一些方法,例如:

second.coolmethod("arg1",2);

我现在的问题是我需要在我的脚本中使用很多 java 类。我也有很多脚本,我认为在每个脚本中定义每一个 类 是非常低效的。

所以我正在寻找一种解决方案来创建在 JavaScript 内部创建的对象,在 Java 内部使用 Java.type(),然后将它们传递给我要执行的脚本。

我该怎么做?

提前致谢!

经过大量研究,我找到了一种在执行前将全局变量放入 ScriptEngine 的方法:The Java Scripting API (Oracle Docs)

这使我能够将我想要的任何对象放入全局变量中。但是,我仍然需要一种方法来获取 Java.type() 在 Java 中创建的对象。所以我写了一个测试脚本,其中 returns 个对象,我发现它是 jdk.internal.dynalink.beans.StaticClass 类型的对象。这个 class 有一个构造函数,它接受一个普通的 Class 作为参数。遗憾的是,此构造函数在我的代码中不可用,因为它不可见。为了绕过这个,我使用了反射并制作了这个方法:

public StaticClass toNashornClass(Class<?> c) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{

    Class<?> cl = Class.forName("jdk.internal.dynalink.beans.StaticClass");

    Constructor<?> constructor = cl.getDeclaredConstructor(Class.class);

    constructor.setAccessible(true);
    StaticClass o = (StaticClass) constructor.newInstance(c);

    return o;
}

如果我将我想要的对象的 Class 作为全局变量传递,我只需要调用 toNashornClass(Example.class); 并将结果对象放入具有 engine.put("example",object);[= 的全局变量中20=]

它工作正常。我可以完全像 Java.type().

创建的 var 一样使用 example var

您可能希望避免在 "jdk.internal."、"jdk.nashorn.internal." 等软件包中使用 "internal" 类。在 jdk9 中,dynalink 是一个 API("jdk.dynalink" 有导出包)。在 jdk9 中,您可以调用 jdk.dyanlink.beans.StaticClass.forClass(Class) [ http://download.java.net/java/jdk9/docs/jdk/api/dynalink/jdk/dynalink/beans/StaticClass.html#forClass-java.lang.Class- ] 来构造 "type" 对象并将它们作为全局变量公开给脚本引擎。对于 jdk8,您可以在评估 "user" 脚本之前预先评估使用 Java.type(String) 调用的脚本。您还可以从 Java 代码调用 "Java.type" 函数。

jdk9的解决方案:

import jdk.dynalink.beans.StaticClass;
import javax.script.*;

public class Main {
   public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");
     e.put("AList", StaticClass.forClass(java.util.ArrayList.class));
     e.eval("var al = new AList(); al.add('hello'), al.add('world')");
     e.eval("print(al)");
   }
}

jdk8的解决方案:

import javax.script.*;

public class Main {
   public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");
     // eval a "boot script" before evaluating user script
     // Note that this script could come from your app resource URL
     e.eval("var AList = Java.type('java.util.ArrayList')");
     // now evaluate user script!
     e.eval("var al = new AList(); al.add('hello'), al.add('world')");
     e.eval("print(al)");
   }
}

jdk8的替代方案:

import javax.script.*;
import jdk.nashorn.api.scripting.*;

public class Main {
   public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");

     // get Java.type function as object
     JSObject javaTypeFunc = (JSObject) e.eval("Java.type");
     // you can javaTypeFunc from java code many times
     Object alType = javaTypeFunc.call(null, "java.util.ArrayList");
     // expose that as global
     e.put("AList", alType);

     // now evaluate user script!
     e.eval("var al = new AList(); al.add('hello'), al.add('world')");
     e.eval("print(al)");
   }
}