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

Posted

技术标签:

【中文标题】用 Rhino 解释 Java 中的 JavaScript:暂停/恢复脚本【英文标题】:Interpreting JavaScript in Java with Rhino: pausing/resuming scripts 【发布时间】:2012-01-26 20:10:19 【问题描述】:

我正在使用 JDK 的 javax.script.* 包。具体来说,我使用的是 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 函数并将其传递给当前 ScriptEngine 或其他任何内容的引用,那对我来说很酷。我认为这可能涉及到:在 Java 中暂停 JavaScript。

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

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

更新:看起来这在 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 库。

【问题讨论】:

【参考方案1】:

好吧,我花了很多时间研究文档、教程和示例,并在此处和 Rhino Google Group 上发帖,但我设法编译了一个可行的解决方案。由于似乎没有完整的示例,因此我将在此处发布我的发现,以供将来偶然发现此问题的任何人使用。

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

Tutorial: Continuations in Mozilla Rhino (a JavaScript interpreter for Java)

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

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

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

【讨论】:

博客站点没有响应。 @JesseGlick 感谢您的提醒。看起来我需要将我的网站移动到新的网络服务器或其他东西。同时,这里有一个 archive.org 版本:web.archive.org/web/20130730231256/http://www.joshforde.com/…【参考方案2】:

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

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 中创建一个打印和一个输入函数来检查程序状态就可以了。

你可以看到demo here.

全部用 javascript 编写,因此您可以使用任何浏览器查看源代码。

希望对你有帮助

【讨论】:

【参考方案3】:

你可以使用等待/通知:

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() 中执行更明智的操作来处理。

【讨论】:

谢谢,但我希望使用的是 Rhino 的实际“延续”功能。经过几天的研究和调试,我终于找到了如何做到这一点,所以我会为将来寻找此信息的任何人发布答案。

以上是关于用 Rhino 解释 Java 中的 JavaScript:暂停/恢复脚本的主要内容,如果未能解决你的问题,请参考以下文章

Rhino 中的 XMLHttpRequest?

Rhino中的directoryList

JavaScript (Rhino) 使用库或包含其他脚本

为啥浏览器可以保存网页中js动态内容,用java编写的爬虫却无法抓取

使用 require.js 和 Java/Rhino 解析模块

使用 Rhino(Mozilla 的 rhino)的优点