当前位置: 首页 > 面试题库 >

捕获Nashorn的全局变量

连昊天
2023-03-14
问题内容

我有一个Java
7程序,该程序加载成千上万个对象(组件),每个对象具有许多参数(存储在中Map),并在这些对象上执行各种Rhino脚本以计算其他派生参数,这些参数又存储回该对象的中Map。在运行每个脚本之前,将Scope创建一个对象,该对象由该对象的映射支持,该映射在脚本运行期间用作JavaScript的作用域。

作为一个简单的示例,以下代码创建一个HashMapa = 10和b = 20的代码,并执行脚本c = a + b,结果c = 30.0将其存储回地图中。尽管脚本看起来像是在创建全局变量c,但是Scope对象会捕获它并将其存储在地图中;使用不同Scope对象执行的另一个脚本将看不到此变量:

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已存储在中Map

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

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输出线显示的结果是正确计算和被存储,但map输出线显示的结果不会被存储在那里我希望他们是。

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

跟随engine.eval()with map.put("c", engine.get("c"))将把结果移动到我需要的位置,但是使用任意脚本,我不知道所有变量名都是什么,因此不是一种选择。

那么问题来了:是否有捕获全局变量的创建并将其存储在应用程序控制下的Java对象中的方法,例如原始Binding对象?


问题答案:

我有一个可行的解决方案,但这显然是一个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);
    }
}

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

GlobalMap截取项将拦截Nashorn全局对象在key下的"nashorn.global"存储,因此不会将其存储在地图中。当GlobalMap关闭时,它会从原地图的犀牛全局对象,并将它们存储任何新的全局变量:

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对象,并将脚本创建的任何新变量直接存储在该对象中。



 类似资料:
  • 如果你要多于一个函数共用一个简单的变量,简单的处理方法就是把这个变量在所有函数中定义为global全局变量。在命令行做同样的事情,如果你要工作空间访问上述变量。这个全局变量的定义必须出现在变量被应用于一个函数之前。虽然不是要求,但全局变量也最好以大写字母开头,这样可以同其他变量区别出来。举个例子,做一个以falling.m命名的M-文件。 function h = falling(t) globa

  • ThinkCMF封装了前台模板开发时常用的一些变量,这些变量是全局的,你在前台模板任何时候都能直接调用: {$site_name} /站点名称 {$site_host} /站点域名 {$site_root} /安装目录 {$site_icp} /

  • swoole内置了几个全局变量供程序内使用。一般不要自行创建全局变量。 SwooleG 超全局本地内存变量,此变量在swoole_init时就初始化好了。存储了一些全局的信息。但不是共享内存的。当创建子进程后再修改其中的字段,其他进程是感知不到的 SwooleG.main_reactor,全局事件循环 SwooleG.lock,全局锁 SwooleG.memory_pool,全局共享内存池 Swo

  • 全局变量 PHP中在函数、类之外直接定义的变量可以在函数、类成员方法中通过global关键词引入使用,这些变量称为:全局变量。 这些直接在PHP中定义的变量(包括include、require文件中的)相对于函数、类方法而言它们是全局变量,但是对自身执行域zend_execute_data而言它们是普通的局部变量,自身执行时它们与普通变量的读写方式完全相同。 function test() {

  • 本文向大家介绍Lua中的全局变量、非全局变量总结,包括了Lua中的全局变量、非全局变量总结的使用技巧和注意事项,需要的朋友参考一下 前言 Lua将其所有的全局变量保存在一个常规的table中,这个table称为“环境”。这种组织结构的优点在于,其一,不需要再为全局变量创造一种新的数据结构,因此简化了Lua的内部实现;另一个优点是,可以像其他table一样操作这个table。为了便于实施这种操作,L

  • 主要内容:局部变量,全局变量,局部变量和全局变量的综合示例在《 C语言形参和实参的区别》中提到,形参变量要等到函数被调用时才分配内存,调用结束后立即释放内存。这说明形参变量的作用域非常有限,只能在函数内部使用,离开该函数就无效了。 所谓 作用域( Scope ) ,就是变量的有效范围。 不仅对于形参变量,C语言中所有的变量都有自己的作用域。决定变量作用域的是变量的定义位置。 局部变量 定义在函数内部的变量称为 局部变量(Local Variable) ,