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

耗尽温柔 提交于 2019-12-12 03:28:35

问题


I am currently executing my JavaScript-scripts with this java code:

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

I need to call Java functions from JavaScript, so I defined this at the top of my awesome_script.js file:

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

I can then call some methods from these classes, e.g.:

second.coolmethod("arg1",2);

My problem is now that I need to use many java classes inside of my scripts. I also have a lot of scripts and I think it is very inefficient to define every single one of this classes in every script.

So I am looking for a solution to create the objects created inside of JavaScript with Java.type() inside of Java and then pass them to the script I want to execute.

How can I do this?

Thanks in advance!


回答1:


You may want to avoid using the "internal" classes in packages like "jdk.internal.", "jdk.nashorn.internal.". In jdk9, dynalink is an API ("jdk.dynalink" has exported packages). In jdk9, you can call 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- ] to construct "type" objects and expose those as global variables to the script engine. For jdk8, you could pre-eval a script that uses Java.type(String) calls before evaluating "user" scripts. You can also call "Java.type" function from Java code.

Solution for 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)");
   }
}

Solution for 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)");
   }
}

Alternative solution for 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)");
   }
}



回答2:


After quite a bit of research I found a way to put global variables in the ScriptEngine before executing: The Java Scripting API (Oracle Docs)

This enabled me to put any object I want into a global variable. However, I still needed a way to get the Object that Java.type() creates inside of Java. So I wrote a test script which returns one of this objects and I found out it is an object of the type jdk.internal.dynalink.beans.StaticClass. This class has an constructor which takes a ordinary Class as an argument. Sadly, this constructor is not usable in my code because it is not visible. To bypass this I used reflection and made this method:

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;
}

If I pass the Class of the object I want as a global variable I just need to call toNashornClass(Example.class); and put the resulting object into a global var with engine.put("example",object);

It works fine. I can use the example var completely like a var created by Java.type().



来源:https://stackoverflow.com/questions/36968431/nashorn-how-to-pre-set-java-type-vars-inside-of-java-before-javascript-execut

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!