用Rhino在Java中解释JavaScript:暂停/恢复脚本

Interpreting JavaScript in Java with Rhino: pausing/resuming scripts

本文关键字:暂停 恢复 脚本 JavaScript 解释 Rhino Java      更新时间:2023-09-26

我正在使用javax.script。JDK的*包。具体来说,我使用的是JavaScript引擎,据我所知,它似乎是基于mozilla开发的JavaScript-in- java解释器Rhino。

我希望完成的是基本上让我的JavaScript能够在代码中的某个点(例如,在函数调用的中途)"暂停"自己,并且只有在Java允许的情况下才恢复自己。

为了说明我的意思,想象一下这个JavaScript代码:

function myJSFunction() {
    print("Hello ");
    mysteriousPauseFunction(); // this is the part I'm wondering about.  basically, the script should break here and resume later at Java's discretion...
    // upon reaching this comment, we know now that Java has told JavaScript that it's okay to resume, so the next line will now be executed...
    print("world");
}

如果"暂停"/"中断"部分涉及绑定Java函数并将其传递给当前scriptenengine的引用或其他内容,那对我来说很酷。我想这可能会涉及到:从Java中暂停JavaScript。

我在谷歌上搜索了一下,发现这里的关键字似乎是"延续"。据我所知,Rhino只支持解释模式下的延续(相对于编译模式),我认为这可以通过将"context"设置为-2来实现。由于内置的JDK ScriptEngine似乎没有提到任何关于上下文的内容(或者可能我遗漏了它),这是否意味着我必须下载并直接使用Mozilla的Rhino库?

我需要Rhino的延续来完成这个吗?我找到了一个关于Rhino延续的有用教程,但是在通读之后,我不能100%确定它是否能够完成我上面描述的内容。如果这个是我正在寻找的,那么我的后续问题是关于提到的"序列化":这是否意味着当我恢复脚本时,除非我序列化它们,否则所有变量都将被取消设置?

Update:看起来这在Rhino中是可能的。这是目前为止我的JavaScript;在代码之后,我将解释它的作用…

var end = new Continuation();
function myJSFunction()
{
    print("Hello ");
    var kont = new Continuation();
    storePause(script, kont); // script is previously bound by Java into the JavaScript.  it is a reference to the script itself.
    end();
    print("world");
}

我的"storePause()"函数是我写的一个Java函数,它被绑定到JavaScript,但现在,它不做任何事情。我的下一个目标是充实它的代码,这样它就可以将延续和脚本信息存储为Java对象,以便Java以后可以恢复脚本。

现在,它所做的是在打印"Hello"之后,但在打印"world"之前暂停/"中断"脚本,所以这向我证明了用这种方式暂停脚本是可能的。

所以,我现在要弄清楚的是如何恢复延续。请注意,上面的工作在默认情况下使用JDK脚本引擎(此时我不需要担心解释模式和编译模式——它似乎默认为解释模式),但看起来恢复脚本的过程将需要Mozilla的Rhino库。

好吧,我花了很多时间挖掘文档、教程和示例,并在这里和Rhino Google Group上发帖,但我已经设法编译了一个工作解决方案。由于似乎没有完整的例子,我将把我的发现张贴在这里,以便将来有人偶然发现这一点。

实际上,我的发现可能太长了,不能在这里发布,所以我决定在我的博客上写一个教程:

教程:Mozilla Rhino(用于Java的JavaScript解释器)中的continuation

希望这能帮助到别人。据我所知,这是唯一一个完整的Rhino教程,它展示了如何做以下所有事情:初始化Rhino,从JavaScript (*.js)文件加载脚本,自动绑定特定Java类(例如ScriptFunctions)中的所有函数作为JavaScript中的全局函数,最后调用JavaScript函数并处理该调用的延续。

基本上,问题是我需要首先下载Mozilla Rhino源代码(因为与JDK一起打包的版本已经过时,不支持延续),重写我所有的代码以使用官方Rhino包的语法(它与JDK的ScriptingEngine语法非常不同),编写一个Java函数,抛出ContinuationPending异常并将其绑定到JavaScript,以便JavaScript可以调用它(因为直接从JavaScript抛出ContinuationPending会导致抛出JavaScriptException,而不是抛出ContinuationPending,甚至试图调用getCause()在JavaScriptException上导致null),然后在我的Java代码中调用我的JavaScript函数("myJSFunction"在我最初的示例中),使用try/catch块来检查ContinuationPending(这是一个异常),然后使用该ContinuationPending稍后恢复脚本。

唷。这很艰难,但现在一切都值得了。

您没有解释为什么要这样做,但是我正在模拟一个与最终用户交互的程序,像这样:

print('Hello!'); 
a=Number(input('enter a number')); 
b=Number(input('and another number')); 
print('the sum of '+a+' plus '+b+' is '+(a+b))

我已经得到了它的工作只是通过创建一个打印和输入函数在javascript检查程序状态。

你可以在这里看到一个演示。

它都是用javascript写的,所以你可以在任何浏览器上查看源代码。

希望有帮助

你可以使用wait/notify:

public final class Pause {
  private final Object lock = new Object();
  public void await() throws InterruptedException {
    synchronized (lock) {
      lock.wait();
    }
  }
  public void resumeAll() {
    synchronized (lock) {
      lock.notifyAll();
    }
  }
}

用法:

final Pause pause = new Pause();
class Resumer implements Runnable {
  @Override public void run() {
    try {
      Thread.sleep(5000);
      pause.resumeAll();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}
new Thread(new Resumer()).start();
SimpleBindings bindings = new SimpleBindings();
bindings.put("pause", pause);
String script = "print('Hello, ');'n"
              + "pause.await();'n"
              + "println('ECMAScript!');'n";
new ScriptEngineManager().getEngineByName("ECMAScript")
                         .eval(script, bindings);

这是一个相对简单的解决方案,因为你没有提到任何其他约束。wait()导致线程阻塞,这在所有环境中都是不可接受的。如果您想并发运行脚本,也没有简单的方法来识别哪些线程正在等待Pause实例。

注意:await()上的InterruptedException应该由调用者处理,或者在await()中做一些更明智的事情。