Java的ScriptEngine本地与全局范围和延迟声明

问题描述 投票:0回答:1

我有一个场景,我正在处理用户提供的包含JavaScript部分的模板。我正在使用Java的ScriptEngine来评估那些JavaScript。我有各种帮助函数,我只想评估一次(想想这些帮助函数加载像Underscore.js这样的东西)。

加载完成后,我遍历数据行并处理每行的模板 - 这些模板可以使用任何以前加载的辅助函数。我试图并行处理行,因为Java的ScriptEngine.eval()在这种情况下非常慢。

任何通过简化SSCCE查看此内容的人都会知道我已经获得了我所展示的预期结果,因为我正在以非线程安全方式修改全局对象。在我的例子中,sJsGlobal是这个全局JavaScript。全局函数需要访问我在obj中放置的各种特定行的数据。

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

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

由于我使用了这个全局obj变量,因此导致以下输出被覆盖,因为它不是线程安全的。即使有一个单独的ScriptEngineManagerScriptEngine。我需要一种设置绑定的方法,这样它们就不会干扰其他实例。

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之前,我真的想找到一种在每个线程中重新定义sJsLocal的方法。

如果我一个接一个地处理每一行而不进行线程化,这一切都可以正常工作,但我试图加快这一点。

java scriptengine
1个回答
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
© www.soinside.com 2019 - 2024. All rights reserved.