Nashorn:如何在JavaScript执行之前预先设置Java.type() - Java内的变量?

时间:2016-05-01 15:31:29

标签: java nashorn

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

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

我需要从JavaScript调用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类。我也有很多脚本,我认为在每个脚本中定义这些类中的每一个都是非常低效的。

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

我该怎么做?

提前致谢!

2 个答案:

答案 0 :(得分:1)

您可能希望避免在“jdk.internal。”,“jdk.nashorn.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-]来构造“类型”对象,并将它们作为全局变量公开给脚本引擎。对于jdk8,您可以在评估“用户”脚本之前预先使用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)");
   }
}

答案 1 :(得分:0)

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

这使我能够将我想要的任何对象放入全局变量中。但是,我仍然需要一种方法来获取Java.type()在Java中创建的Object。所以我编写了一个测试脚本,它返回一个这样的对象,我发现它是一个jdk.internal.dynalink.beans.StaticClass类型的对象。这个类有一个构造函数,它以普通的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);

的全局变量中

工作正常。我可以完全像example创建的var一样使用Java.type() var。