【问题标题】:Calling Ruby method from Java从 Java 调用 Ruby 方法
【发布时间】:2017-10-03 19:12:50
【问题描述】:

我有一个依赖于 ruby​​ 脚本结果的 Java 应用程序。我面临的问题是这个 ruby​​ 脚本被多次调用,每次执行时都需要加载脚本的库。

我不熟悉 Ruby,但在搜索中我遇到了 JRuby。因为它解释了 ruby​​ 代码并在 JVM 上运行,所以我最初的想法是“太好了,我可以将 ruby​​ 脚本编译为 .class 文件将其打包并像常规 java 对象一样与它交互”。这将避免每次执行 ruby​​ 脚本时加载库所带来的开销。

在深入研究了 jruby 之后,我了解到它不能以这种方式工作。所以要做我想做的事,我可以使用 JRuby 的 JavaEmbedUtils 来

  1. 使用我的 Ruby 脚本加载 Ruby 运行时对象
  2. 创建一个基本上知道如何处理和响应我的脚本的接收器对象。
  3. 从脚本调用所需的方法。

这是我的想法(我很快就会测试)

// 1  
List<String> paths = new ArrayList<>();  
paths.add(".");  
Ruby runtime = JavaEmbedUtils.initialize(paths);

// 2  
String script = "/path/to/script.rb";  
IRubyObject recvr = JavaEmbedUtils.newRuntimeAdapter().eval(runtime, script);

// 3 Call this many times   
JavaEmbedUtils.invokeMethod(runtime, receiver, "method", null, null);

我的理解是否正确,因为这种方法允许我在加载脚本库一次的同时多次使用脚本的内容?是否有替代或更多的 JRuby 方式来做我正在寻找的事情?

更新

所以我测试了类似于 Eugene 建议的东西 (a),即 ScriptingContainer,并将其与使用 (b) java.lang.Runtime 调用脚本进行了比较,总共运行了 30 次。

  1. 平均而言,(a) 大约快 8 倍 (b)
  2. 初始化 (a) 的运行时间大约需要 7 秒
  3. (a) 和 (b) 的初始运行时间比后续运行时间长 40 倍和 150-250 倍。
  4. (a) 的初始运行速度比 (b)s 快 11 倍

【问题讨论】:

  • 答案已更新。

标签: java ruby jruby


【解决方案1】:

这就是我在 java 代码的脚本容器中使用 JRuby 的方式:

import org.jruby.*;
import org.jruby.embed.LocalVariableBehavior;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;

ScriptingContainer container = new ScriptingContainer(LocalVariableBehavior.PERSISTENT);
container.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
container.setNativeEnabled(false);
container.setObjectSpaceEnabled(true);
container.put("some_param", someValue);

// My script return an array - tweak to fit your returning value
RubyArray resourceArray = (RubyArray) container.runScriptlet(PathType.CLASSPATH, scriptPath);

我必须注意 JRuby 在启动时太慢(这些​​数字不太精确,但在我的情况下,即使在 4Ghz + SSD 上也是 2-3 秒)。 TDD 因这种延迟而变得痛苦。这就是为什么对对象空间等进行一些调整的原因。

另外,我必须删除这个模块,以便测试的其余部分使用其他固定装置并在不启动 JRuby 的情况下运行。换句话说,我不必在单独运行另一个测试时启动它。

附言

我的理解是否正确,因为这种方法要求我只加载脚本的依赖项一次?

我不确定你在这里是什么意思,但似乎在使用这种方法时,JRuby 端“需要”的所有依赖项每次都会被加载。

无论如何,我会先做一个基准测试。也许不值得考虑。

UPD:

这比我想象的要简单:

common.rb:

puts "common"

script.rb:

require 'common'

puts "script"

代码:

ScriptingContainer container = new ScriptingContainer(LocalVariableBehavior.PERSISTENT);
for (int i = 0; i < 3; i++) {
    container.runScriptlet(PathType.CLASSPATH, "script.rb");
}

输出:

common
script
script
script

【讨论】:

  • 感谢您的反馈,但如果我想运行特定方法,这是否必须评估整个脚本(每次加载库)。这个容器是否缓存脚本加载的库,这样我每次想从中调用方法时都不必重新加载它们?如果我多次调用脚本的内容,还有 compileMode.OFF 的好处吗?
  • 我认为“compileMode”需要一些分析。在我的情况下,我在服务器启动时加载它一次,以便调整执行 TDD 的启动时间。我会尽快更新我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 2023-03-21
  • 1970-01-01
  • 2014-04-05
  • 2016-03-26
  • 2018-09-22
  • 2019-10-08
相关资源
最近更新 更多