捕获Nashorn的全局变量 [英] Capturing Nashorn's Global Variables

查看:121
本文介绍了捕获Nashorn的全局变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Java 7程序,它加载了数千个对象(组件),每个对象都有许多参数(存储在 Map 中),并在那些上执行各种Rhino脚本对象计算其他派生参数,这些参数存储在对象的 Map 中。在运行每个脚本之前,会创建一个 Scope 对象,该对象由对象的映射支持,该映射在脚本持续时间内用作JavaScript的范围。

I have a Java 7 program, that loads thousands of objects (components), each with many parameters (stored in a Map), and executes various Rhino scripts on those objects to calculate other derived parameters which get stored back in the object's Map. Before each script is run, a Scope object is created, backed by the object's map, which is used as a JavaScript's scope for the duration of the script.

作为一个简单的例子,下面创建一个 HashMap ,其中a = 10且b = 20,并执行脚本 c = a + b ,这会导致 c = 30.0 存储在地图中。虽然脚本看起来像是在创建一个全局变量 c ,但 Scope 对象会捕获它并将其存储在地图中;使用不同的 Scope 对象执行的另一个脚本将看不到此变量:

As a simple example, the following creates a HashMap with a=10 and b=20, and executes the script c = a + b, which results in c = 30.0 stored back in the map. Although the script looks like it is creating a global variable c, the Scope object captures it and stores it in the map; another script executed with a different Scope object won't see this variable:

public class Rhino {

    public static void main(String[] args) throws ScriptException {
        Context cx = Context.enter();
        Scriptable root_scope = cx.initStandardObjects();

        Map<String, Object> map = new HashMap<>();
        map.put("a", 10);
        map.put("b", 20);

        Scope scope = new Scope(root_scope, map);
        cx.evaluateString(scope, "c = a + b", "<expr>", 0, null);
        System.out.println(map); // --> {b=20, c=30.0, a=10}

        Context.exit();
    }

    static class Scope extends ScriptableObject {

        private Map<String, Object> map;

        public Scope(Scriptable parent, Map<String, Object> map) {
            setParentScope(parent);
            this.map = map;
        }

        @Override
        public boolean has(String key, Scriptable start) {
            return true;
        }

        @Override
        public Object get(String key, Scriptable start) {
            if (map.containsKey(key))
                return map.get(key);
            return Scriptable.NOT_FOUND;
        }

        @Override
        public void put(String key, Scriptable start, Object value) {
            map.put(key, value);
        }

        @Override
        public String getClassName() {
            return "MapScope";
        }
    }
}

以上脚本输出 {b = 20,c = 30.0,a = 10} ,显示变量 c 已存储在<$ c中$ c> Map 。

The above script outputs {b=20, c=30.0, a=10}, showing the variable c has been stored in the Map.

现在,我需要将它迁移到Java 8,并使用Nashorn。但是,我发现Nashorn总是将全局变量存储在一个特殊的nashorn.global对象中。实际上,它似乎将所有绑定视为只读,并且尝试更改现有变量会导致新的全局变量遮蔽现有绑定。

Now, I need to migrate this the Java 8, and use Nashorn. However, I am finding that Nashorn always stores global variables in a special "nashorn.global" object. In fact, it seems to be treating all bindings as read-only, and attempts to change an existing variable instead results in a new global variable shadowing the existing binding.

public class Nashorn {

    private final static ScriptEngineManager MANAGER = new ScriptEngineManager();

    public static void main(String[] args) throws ScriptException {
        new Nashorn().testBindingsAsArgument();
        new Nashorn().testScopeBindings("ENGINE_SCOPE", ScriptContext.ENGINE_SCOPE);
        new Nashorn().testScopeBindings("GLOBAL_SCOPE", ScriptContext.GLOBAL_SCOPE);
    }

    private ScriptEngine engine = MANAGER.getEngineByName("nashorn");
    private Map<String, Object> map = new HashMap<>();
    private Bindings bindings = new SimpleBindings(map);

    private Nashorn() {
        map.put("a", 10);
        map.put("b", 20);
    }

    private void testBindingsAsArgument() throws ScriptException {
        System.out.println("Bindings as argument:");
        engine.eval("c = a + b; a += b", bindings);
        System.out.println("map = " + map);
        System.out.println("eval('c', bindings) = " + engine.eval("c", bindings));
        System.out.println("eval('a', bindings) = " + engine.eval("a", bindings));
    }

    private void testScopeBindings(String scope_name, int scope) throws ScriptException {
        System.out.println("\n" + scope_name + ":");
        engine.getContext().setBindings(bindings, scope);
        engine.eval("c = a + b; a += b");
        System.out.println("map = " + map);
        System.out.println("eval('c') = " + engine.eval("c"));
        System.out.println("eval('a') = " + engine.eval("a"));
    }
}

输出:

Bindings as argument:
map = {a=10, b=20, nashorn.global=[object global]}
eval('c', bindings) = 30.0
eval('a', bindings) = 30.0

ENGINE_SCOPE:
map = {a=10, b=20, nashorn.global=[object global]}
eval('c') = 30.0
eval('a') = 30.0

GLOBAL_SCOPE:
map = {a=10, b=20}
eval('c') = 30.0
eval('a') = 30.0

eval 输出行显示结果已正确计算并存储,但地图输出行显示结果未存储在我希望的位置。

The eval output lines show the results are correctly computed and are being stored, but the map output lines shows the results are not being stored where I desire them to be.

由于各种原因,这是不可接受的。单个对象不会将计算出的参数存储回自己的本地存储中。来自其他对象上执行的其他脚本的变量将从以前的脚本执行中继承,这可能会隐藏逻辑错误(脚本可能会意外地使用未定义的变量名称,但如果该名称实际上是由前一个脚本使用的,那么旧的垃圾值可以被用来代替 ReferenceError 正在生成,隐藏错误。)

This is not acceptable, for a variety of reasons. The individual objects do not get the computed parameters stored back in their own local storage. Variables from other scripts executing on other objects will carry over from previous script executions, which could hide logic errors (a script could accidentally use an undefined variable name, but if it that name was actually used by a previous script, the old garbage value could be used instead of a ReferenceError being generated, hiding the error).

之后engine.eval() with map.put(c,engine.get(c))会将结果移动到我的位置需要它,但有一个任意的脚本,我不知道所有的变量名称是什么,所以不是一个选项。

Following the engine.eval() with map.put("c", engine.get("c")) would move the result to where I need it to be, but with an arbitrary script, I do not know what all the variable names would be, so is not an option.

所以问题:是吗无论如何捕获全局变量的创建,并将它们存储在应用程序控制下的Java对象中,例如原始的Binding对象??

So the question: is there anyway to capture the creation of global variables, and store them instead inside a Java object under control of the application, such as the original Binding object??

推荐答案

我有一个似乎有效的解决方案,但它显然是一个黑客。

I have a solution that seems to work, but it clearly is a hack.

测试程序:

public class Nashorn {
    public static void main(String[] args) throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");

        Map<String, Object> map = new HashMap<>();
        map.put("a", 10);
        map.put("b", 20);

        try (GlobalMap globals = new GlobalMap(map)) {
            engine.eval("c = a + b; a += b;", globals);
        }

        System.out.println("map = " + map);
    }
}

测试程序输出地图= {a = 30.0,b = 20,c = 30.0} 根据需要。

The test program outputs map = {a=30.0, b=20, c=30.0} as desired.

GlobalMap 拦截在密钥nashorn.global下存储Nashorn全局对象,因此它不会存储在地图中。当 GlobalMap 关闭时,它会从Nashorn全局对象中删除所有新的全局变量并将它们存储在原始地图中:

The GlobalMap intercepts the storing of the Nashorn global object under the key "nashorn.global", so it doesn't get stored in the map. When the GlobalMap is closed, it removes any new global variables from the Nashorn global object and stores them in the original map:

public class GlobalMap extends SimpleBindings implements Closeable {

    private final static String NASHORN_GLOBAL = "nashorn.global";
    private Bindings global;
    private Set<String> original_keys;

    public GlobalMap(Map<String, Object> map) {
        super(map);
    }

    @Override
    public Object put(String key, Object value) {
        if (key.equals(NASHORN_GLOBAL) && value instanceof Bindings) {
            global = (Bindings) value;
            original_keys = new HashSet<>(global.keySet());
            return null;
        }
        return super.put(key, value);
    }

    @Override
    public Object get(Object key) {
        return key.equals(NASHORN_GLOBAL) ? global : super.get(key);
    }

    @Override
    public void close() {
        if (global != null) {
            Set<String> keys = new HashSet<>(global.keySet());
            keys.removeAll(original_keys);
            for (String key : keys)
                put(key, global.remove(key));
        }
    }
}

我仍然希望找到一种解决方案,其中当前范围可以设置为 Map< String,Object> Bindings 对象,以及任何新的脚本创建的变量直接存储在该对象中。

I am still hoping to find a solution where the current scope could be set to a Map<String,Object> or Bindings object, and any new variables created by the script are stored directly in that object.

这篇关于捕获Nashorn的全局变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆