Java的ScriptEngine局部作用域与全局作用域和延迟声明

时间:2018-11-30 12:20:37

标签: java scriptengine

我有一种情况,正在处理用户提供的包含JavaScript部分的模板。我正在使用Java的intent.extras?.putByteArray(event, response.toByteArray()) [...] var list = Arrays.asList(intent.extras?.getByteArray(event)) 来评估那些JavaScript。我有各种各样的助手功能,我只希望对其进行一次评估(将这些助手功能视为加载ScriptEngine之类的东西)。

一旦加载完成,我便遍历数据行并处理每一行的模板-这些模板可以使用任何先前加载的帮助器函数。我正在尝试并行处理行,因为在这种情况下Java的Underscore.js很慢。

任何通过简化的SSCCE进行研究的人都会知道,我正在以非线程安全的方式修改全局对象,从而获得了预期的结果。在我的示例中,ScriptEngine.eval()是此全局JavaScript。全局函数需要访问我放置在sJsGlobal中的各种行特定的数据位。

要使obj成功sJsGlobal,它需要了解eval(),否则将会失败。在我的SSCCE中,每个线程在调用函数之前都更新相同的全局obj。我正在为每个全局函数寻找一种查看本地obj的方法。这是Catch-22的情况,我需要为全局脚本定义变量才能知道它的存在,但我希望它具有局部意义。

obj

这将导致以下输出,由于我使用此全局import java.util.*; import java.util.concurrent.*; import javax.script.*; public class SSCCE { public static void main(String[] args) throws Exception { StringBuilder sJsGlobal = new StringBuilder(); sJsGlobal.append("var obj = {};"); sJsGlobal.append("function get_row() { return \"Row is \" + obj.row }"); ScriptEngine jsGlobal = new ScriptEngineManager().getEngineByName("JavaScript"); jsGlobal.eval(sJsGlobal.toString()); Bindings gB = jsGlobal.getBindings(ScriptContext.ENGINE_SCOPE); ExecutorService es = Executors.newCachedThreadPool(); ArrayList<Future<String>> af = new ArrayList<Future<String>>(); for (int i = 0; i < 10; i++) { final int fi = i; af.add(es.submit(() -> { StringBuilder sJsLocal = new StringBuilder(); sJsLocal.append("obj.row = " + fi + ";"); sJsLocal.append("var x = get_row();"); ScriptEngine jsLocal = new ScriptEngineManager().getEngineByName("JavaScript"); ScriptContext sc = new SimpleScriptContext(); Bindings lB = jsLocal.createBindings(); lB.putAll(gB); sc.setBindings(lB, ScriptContext.ENGINE_SCOPE); jsLocal.setContext(sc); jsLocal.eval(sJsLocal.toString()); return (String)jsLocal.get("x"); })); } for (Future<String> f : af) { System.out.println("Returned -> " + f.get()); } es.shutdown(); } } 变量,因此行不安全,因此行被覆盖。即使使用单独的objScriptEngineManager。我需要一种设置绑定的方法,以便它们不会干扰其他实例。

ScriptEngine

我可以将行特定的数据作为变量传递给每个函数,但这变得非常混乱,因为我的示例对我需要交换的数据量不公道。用户脚本也调用这些函数,这使此操作非常困难。我真的很想找到一种在评估Returned -> Row is 4 Returned -> Row is 4 Returned -> Row is 4 Returned -> Row is 4 Returned -> Row is 8 Returned -> Row is 5 Returned -> Row is 6 Returned -> Row is 7 Returned -> Row is 8 Returned -> Row is 9 之前在每个线程中重新定义obj的方法。

如果我逐行处理每一行而不使用线程,那么一切都很好,但是我正在尝试加快速度。

1 个答案:

答案 0 :(得分:0)

经过大量的尝试,错误和努力,并且阅读了许多相似的问题而没有答案,我提出了以下似乎有效的方法,并在此处记录了遇到此问题的其他任何人。这使用一个公用的ScriptEngine和每个线程一个单独的ScriptContext来提供隔离。

import java.util.*;
import java.util.concurrent.*;
import javax.script.*;

public class SSCCE {
  public static void main(String[] args) throws Exception {
    ScriptEngine js = new ScriptEngineManager().getEngineByName("JavaScript");

    StringBuilder sJsGlobal = new StringBuilder();
    sJsGlobal.append("var obj = {};");
    sJsGlobal.append("function get_row() { return \"Row is \" + obj.row; }"); 
    CompiledScript jsLib = ((Compilable)js).compile(sJsGlobal.toString());

    ExecutorService es = Executors.newCachedThreadPool();
    ArrayList<Future<String>> af = new ArrayList<Future<String>>();

    for (int i = 0; i < 10; i++) {
      final int fi = i;
      af.add(es.submit(() -> {
        StringBuilder sJsLocal = new StringBuilder();
        sJsLocal.append("obj.row = " + fi + ";");
        sJsLocal.append("var x = get_row();");

        ScriptContext scLocal = new SimpleScriptContext();
        scLocal.setBindings(js.createBindings(), ScriptContext.ENGINE_SCOPE);
        jsLib.eval(scLocal);
        js.eval(sJsLocal.toString(), scLocal);
        return (String)scLocal.getAttribute("x");
      }));
    }

    for (Future<String> f : af) {
      System.out.println("Returned -> " + f.get());
    }
    es.shutdown();
  }
}

这将在运行时产生以下结果:

Returned -> Row is 0
Returned -> Row is 1
Returned -> Row is 2
Returned -> Row is 3
Returned -> Row is 4
Returned -> Row is 5
Returned -> Row is 6
Returned -> Row is 7
Returned -> Row is 8
Returned -> Row is 9