- 适用于 JDK 23 的 GraalVM(最新)
- 适用于 JDK 24 的 GraalVM(抢先体验版)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发版构建
从 JRuby 迁移到 TruffleRuby
在您的 gems 和应用程序上尝试 TruffleRuby 时,我们鼓励您 与 TruffleRuby 团队联系 以获取帮助。
部署 #
如果您要从 JRuby 迁移,使用 TruffleRuby 最简单的方法可能是安装 TruffleRuby JVM 独立版。
如果您不需要 TruffleRuby 的 Java 互操作性功能,则可以安装 TruffleRuby 本机独立版。
从 Java 使用 Ruby #
JRuby 支持许多不同的方式将 Ruby 嵌入 Java 中,包括 JSR 223(也称为 javax.script
)、Bean Scripting Framework (BSF)、JRuby Embed(也称为 Red Bridge)和 JRuby 直接嵌入 API。
嵌入 TruffleRuby 的最佳方式是使用 Polyglot API。该 API 不同,因为它被设计为支持多种语言,而不仅仅是 Ruby。
TruffleRuby 还支持与 JRuby 兼容的 JSR 223,以便更轻松地运行遗留的 JRuby 代码。请参阅 本说明文档 以了解如何使用它。
您需要使用 JVM 独立版或依赖 org.graalvm.polyglot:polyglot
Maven 包才能使用 Polyglot API。
请参阅 polyglot 文档以了解有关如何从其他语言(包括 Java)使用 Ruby 的更多信息;本文档仅显示与 JRuby 的比较。
创建上下文 #
在 JRuby 中使用 JSR 223,您会编写以下内容
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine scriptEngine = m.getEngineByName("ruby");
或者在 BSF 中,您会编写以下内容
BSFManager.registerScriptingEngine("jruby", "org.jruby.embed.bsf.JRubyEngine", null);
BSFManager bsfManager = new BSFManager();
或者在 JRuby Embed 中,您会编写以下内容
ScriptingContainer container = new ScriptingContainer();
或者在直接嵌入 API 中,您会编写以下内容
Ruby ruby = Ruby.newInstance(new RubyInstanceConfig());
在 TruffleRuby 中,您现在编写以下内容
Context polyglot = Context.newBuilder().allowAllAccess(true).build();
allowAllAccess(true)
方法允许 Ruby 需要完全功能的宽松访问权限。GraalVM 默认情况下会禁止许多可能不安全的权限,例如本地文件访问,但是正常 Ruby 安装使用这些权限,因此我们启用了它们。您可以选择不授予这些权限,但这会限制 Ruby 的某些功能。
// No privileges granted, restricts functionality
Context polyglot = Context.newBuilder().build();
通常,您会在 try
块内创建上下文以确保其被正确释放
try (Context polyglot = Context.newBuilder().allowAllAccess(true).build()) {
}
请参阅 Context API 以了解有关 Context
的详细文档。
设置选项 #
您可以通过系统属性或通过 .option(name, value)
生成器方法来设置 TruffleRuby 选项。
评估代码 #
在 JRuby 中,您会编写以下 JRuby 示例之一,提供的可用选项是
scriptEngine.eval("puts 'hello'");
bsfManager.exec("jruby", "<script>", 1, 0, "puts 'hello'");
container.runScriptlet("puts 'hello'");
ruby.evalScriptlet("puts 'hello'");
在 TruffleRuby 中,您现在编写以下内容
polyglot.eval("ruby", "puts 'hello'");
请注意,eval
支持多种语言,因此您需要每次都指定语言。
使用参数评估代码 #
在 JRuby 中使用 JSR 223,您可以将参数(称为绑定)传递到脚本中
Bindings bindings = scriptEngine.createBindings();
bindings.put("a", 14);
bindings.put("b", 2);
scriptEngine.eval("puts a + b", bindings);
在 TruffleRuby 中,eval
方法不接受参数。相反,您应该返回一个接受参数的 proc,然后对该值调用 execute
polyglot.eval("ruby", "-> a, b { puts a + b }").execute(14, 2);
原始值 #
不同的嵌入 API 以不同的方式处理原始值。在 JSR 223、BSF 和 JRuby Embed 中,返回类型为 Object
,可以将其强制转换为类似 long
的原始类型并使用 instanceof
检查。在直接嵌入 API 中,返回的是根 IRubyObject
接口,您需要将原始类型转换为 Integer
,然后从那里转换为 Java long
(long) scriptEngine.eval("14 + 2");
(long) bsfManager.eval("jruby", "<script>", 1, 0, "14 + 2");
(long) container.runScriptlet("14 + 2");
ruby.evalScriptlet("14 + 2").convertToInteger().getLongValue();
在 TruffleRuby 中,返回值始终是封装的 Value
对象,如果对象允许,可以将其访问为 long
。fitsInLong()
可以测试这一点
polyglot.eval("ruby", "14 + 2").asLong();
调用方法 #
要调用从 eval
或任何其他对象获取的对象上的方法,在 JRuby 嵌入 API 中,您需要请求上下文调用该方法,或者在直接嵌入的情况下,您需要调用接收器上的方法并将参数自己编组到 JRuby 类型中。BSF 似乎没有调用方法的方法
((Invocable) scriptEngine).invokeMethod(scriptEngine.eval("Math"), "sin", 2);
container.callMethod(container.runScriptlet("Math"), "sin", 2);
ruby.evalScriptlet("Math").callMethod(ruby.getCurrentContext(), "sin", new IRubyObject[]{ruby.newFixnum(2)})
在 TruffleRuby 中,Value
类有一个 getMember
方法可以返回对象上的 Ruby 方法,然后您可以通过调用 execute
来调用它。您无需编组参数
polyglot.eval("ruby", "Math").getMember("sin").execute(2);
要调用原始类型上的方法,请使用 lambda
polyglot.eval("ruby", "-> x { x.succ }").execute(2).asInt();
传递块 #
块是 Ruby 特定的语言特性,因此它们不会出现在类似 JSR 223 和 BSF 的语言无关 API 中。JRuby Embed API 和直接嵌入允许将 Block
参数传递给 callMethod
方法,但目前尚不清楚如何创建 Block
对象来使用它。
在 TruffleRuby 中,您应该返回一个执行调用的 Ruby lambda,传递一个执行您传入的 Java lambda 的块
polyglot.eval("ruby", "-> block { (1..3).each { |n| block.call n } }")
.execute(polyglot.asValue((IntConsumer) n -> System.out.println(n)));
创建对象 #
JRuby 嵌入 API 不支持创建新对象,但您可以自己调用 new
方法
((Invocable) scriptEngine).invokeMethod(scriptEngine.eval("Time"), "new", 2021, 3, 18);
container.callMethod(container.runScriptlet("Time"), "new", 2021, 3, 18)
ruby.evalScriptlet("Time").callMethod(ruby.getCurrentContext(), "new",
new IRubyObject[]{ruby.newFixnum(2021), ruby.newFixnum(3), ruby.newFixnum(8)})
在 TruffleRuby 中,您可以使用 newInstance
从 Ruby class
创建对象。您可以使用 canInstantiate
来查看这是否可行
polyglot.eval("ruby", "Time").newInstance(2021, 3, 18);
处理字符串 #
在 JRuby 的嵌入 API 中,您会使用 toString
将其转换为 Java String
。在 TruffleRuby 中使用 asString
(并使用 isString
检查)。
访问数组 #
JRuby 的数组实现了 List<Object>
,因此您可以将其强制转换为该接口以访问它们
((List) scriptEngine.eval("[3, 4, 5]")).get(1);
((List) container.runScriptlet("[3, 4, 5]")).get(1);
((List) bsfManager.eval("jruby", "<script>", 1, 0, "[3, 4, 5]")).get(1);
((List) ruby.evalScriptlet("[3, 4, 5]")).get(1);
在 TruffleRuby 中,您可以使用 getArrayElement
、setArrayElement
和 getArraySize
,或者您可以使用 as(List.class)
来获取 List<Object>
polyglot.eval("ruby", "[3, 4, 5]").getArrayElement(1);
polyglot.eval("ruby", "[3, 4, 5]").as(List.class).get(1);
访问哈希表 #
JRuby 的哈希表实现了 Map<Object, Object>
,因此您可以将其强制转换为该接口以访问它们
((Map) scriptEngine.eval("{'a' => 3, 'b' => 4, 'c' => 5}")).get("b");
((Map) scriptEngine.eval("{3 => 'a', 4 => 'b', 5 => 'c'}")).get(4);
在 TruffleRuby 中,目前还没有统一的方式来访问哈希表或类似字典的数据结构。目前,我们建议使用 lambda 访问器
Value hash = polyglot.eval("ruby", "{'a' => 3, 'b' => 4, 'c' => 5}");
Value accessor = polyglot.eval("ruby", "-> hash, key { hash[key] }");
accessor.execute(hash, "b");
实现接口 #
您可能希望使用 Ruby 对象来实现 Java 接口(示例复制自 JRuby wiki)
interface FluidForce {
double getFluidForce(double a, double b, double depth);
}
class EthylAlcoholFluidForce
def getFluidForce(x, y, depth)
area = Math::PI * x * y
49.4 * area * depth
end
end
EthylAlcoholFluidForce.new
String RUBY_SOURCE = "class EthylAlcoholFluidForce\n def getFluidForce...";
在 JSR 223 中,您可以使用 getInterface(object, Interface.class)
。在 JRuby Embed 中,您可以使用 getInstance(object, Interface.class)
。在直接嵌入中,您可以使用 toJava(Interface.class)
。BSF 似乎不支持实现接口
FluidForce fluidForce = ((Invocable) scriptEngine).getInterface(scriptEngine.eval(RUBY_SOURCE), FluidForce.class);
FluidForce fluidForce = container.getInstance(container.runScriptlet(RUBY_SOURCE), FluidForce.class);
FluidForce fluidForce = ruby.evalScriptlet(RUBY_SOURCE).toJava(FluidForce.class);
fluidForce.getFluidForce(2.0, 3.0, 6.0);
在 TruffleRuby 中,您可以使用 as(Interface.class)
来获取 Ruby 对象实现的接口
FluidForce fluidForce = polyglot.eval("ruby", RUBY_SOURCE).as(FluidForce.class);
fluidForce.getFluidForce(2.0, 3.0, 6.0);
JRuby 允许 Ruby 方法的名称为 get_fluid_force
,使用 Ruby 约定,而不是 getFluidForce
,使用 Java 约定。TruffleRuby 目前不支持这一点。
实现 Lambda #
据我们所知,JSR 223、BSF、JRuby Embed 和直接嵌入没有方便的方法可以从 Ruby lambda 获取 Java lambda。
在 TruffleRuby 中,您可以使用 as(FunctionalInterface.class)
从 Ruby lambda 获取 Java lambda(实际上是函数接口的实现)
BiFunction<Integer, Integer, Integer> adder = polyglot.eval("ruby", "-> a, b { a + b }").as(BiFunction.class);
adder.apply(14, 2).intValue();
解析一次运行多次 #
一些 JRuby 嵌入 API 允许脚本编译一次,然后评估多次
CompiledScript compiled = ((Compilable) scriptEngine).compile("puts 'hello'");
compiled.eval();
在 TruffleRuby 中,您可以简单地从解析中返回一个 lambda,然后执行它多次。它将像其他任何 Ruby 代码一样受到优化
Value parsedOnce = polyglot.eval("ruby", "-> { run many times }");
parsedOnce.execute();
从 Ruby 使用 Java #
TruffleRuby 提供了它自己的 Java 互操作性方案,该方案对于从任何 GraalVM 语言到任何其他 GraalVM 语言的使用都是一致的。这与现有的 JRuby-Java 互操作性不兼容,因此您需要迁移。
一般来说,多语言编程在 其他地方有文档记载 - 本节描述了它相对于 JRuby 的内容。
此示例来自 JRuby wiki
require 'java'
# With the 'require' above, you now can refer to things that are part of the
# standard Java platform via their full paths.
frame = javax.swing.JFrame.new("Window") # Creating a Java JFrame
label = javax.swing.JLabel.new("Hello World")
# You can transparently call Java methods on Java objects, just as if they were defined in Ruby.
frame.add(label) # Invoking the Java method 'add'.
frame.setDefaultCloseOperation(javax.swing.WindowConstants::EXIT_ON_CLOSE)
frame.pack
frame.setVisible(true)
sleep
在 TruffleRuby 中,我们会改为以下方式编写
Java.import 'javax.swing.JFrame'
Java.import 'javax.swing.JLabel'
Java.import 'javax.swing.WindowConstants'
frame = JFrame.new("Window")
label = JLabel.new("Hello World")
frame.add(label)
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.pack
frame.setVisible(true)
sleep
我们没有使用 Ruby 元编程来模拟 Java 包名,而是明确地导入了类。Java.import
与 JRuby 的 java_import
类似,并执行 ClassName = Java.type('package.ClassName')
。
常量是通过读取类的属性而不是使用 Ruby 表示法来读取的。
需要 Java #
不要在 TruffleRuby 中使用 require 'java'
。但是,您确实需要在 --jvm
模式下运行。这在 Native Standalone 中不可用。
引用类 #
在 JRuby 中,Java 类可以在 Java
模块中引用,例如 Java::ComFoo::Bar
,或者如果它们有共同的 TLD,则可以将其引用为 com.foo.Bar
。java_import com.foo.Bar
将定义 Bar
作为顶级常量。
在 TruffleRuby 中,Java 类使用 Java.type('com.foo.Bar')
引用,您通常会将其分配给常量,或者可以使用 Java.import 'com.foo.Bar'
使 Bar
在封闭模块中定义。
通配符包导入 #
JRuby 允许您使用 include_package 'com.foo'
,这将使该包中的所有类都作为当前范围内的常量可用。
在 TruffleRuby 中,您显式地引用类。
调用方法和创建实例 #
在 JRuby 和 TruffleRuby 中,您都像调用 Ruby 方法一样调用 Java 方法。
JRuby 将重写类似 my_method
的方法名以符合 Java 约定 myMethod
,并将 getFoo
转换为 foo
,并将 setFoo
转换为 foo=
。TruffleRuby 不执行这些转换。
调用重载方法 #
当多个重载可能时,需要明确地选择它。例如,java.util.concurrent.ExecutorService
既有 submit(Runnable)
,也有 submit(Callable<T> task)
。在没有指定哪个的情况下调用 submit 会显示可能的重载
$ ruby -e 'Java.type("java.util.concurrent.Executors").newFixedThreadPool(1).submit {}'
-e:1:in `main': Multiple applicable overloads found for method name submit (candidates: [
Method[public java.util.concurrent.Future java.util.concurrent.AbstractExecutorService.submit(java.util.concurrent.Callable)],
Method[public java.util.concurrent.Future java.util.concurrent.AbstractExecutorService.submit(java.lang.Runnable)]],
arguments: [RubyProc@4893b344 (RubyProc)]) (TypeError)
您可以使用以下方法选择特定的重载。
executor = Java.type("java.util.concurrent.Executors").newFixedThreadPool(1)
executor['submit(java.lang.Runnable)'].call(-> { 1 })
# or
executor.send("submit(java.lang.Runnable)") { 1 }
参考常量 #
在 JRuby 中,Java 常量被建模为 Ruby 常量,例如 MyClass::FOO
。在 TruffleRuby 中,您可以使用读符号作为属性读取它们,例如 MyClass.FOO
或 MyClass[:FOO]
。
使用来自 JAR 文件的类 #
在 JRuby 中,您可以使用 require
将类和 JAR 添加到类路径。在 TruffleRuby 中,您目前可以使用 -classpath
JVM 标志作为正常操作。
其他 Java 特定方法 #
JRuby 在 Java 对象上定义了这些方法;请改用以下等效方法。
java_class
- 使用 class
。
java_kind_of?
- 使用 is_a?
java_object
- 不支持。
java_send
- 使用 __send__
。
java_method
- 不支持。
java_alias
- 不支持。
创建 Java 数组 #
在 JRuby 中,您可以使用 Java::byte[1024].new
。
在 TruffleRuby 中,您可以使用 Java.type('byte[]').new(1024)
。
实现 Java 接口 #
JRuby 有几种方法可以实现接口。例如,要向 Swing 按钮添加操作侦听器,我们可以执行以下三种操作中的任何一种。
class ClickAction
include java.awt.event.ActionListener
def actionPerformed(event)
javax.swing.JOptionPane.showMessageDialog nil, 'hello'
end
end
button.addActionListener ClickAction.new
button.addActionListener do |event|
javax.swing.JOptionPane.showMessageDialog nil, 'hello'
end
button.addActionListener -> event {
javax.swing.JOptionPane.showMessageDialog nil, 'hello'
}
在 TruffleRuby 中,我们始终使用最后一个选项来生成接口。
button.addActionListener -> event {
JOptionPane.showMessageDialog nil, 'hello'
}
在运行时生成 Java 类 #
JRuby 支持使用 become_java!
将 Ruby 类转换为具体的 Java 类。
TruffleRuby 不支持此操作。我们建议您使用适当的 Java 接口作为 Java 和 Ruby 之间的接口。
重新打开 Java 类 #
在 TruffleRuby 中无法重新打开 Java 类。
对 Java 类进行子类化 #
在 TruffleRuby 中无法对 Java 类进行子类化。请改用组合或接口。
使用 Java 扩展 TruffleRuby #
JRuby 支持用 Java 编写的扩展。这些扩展针对一个非正式接口编写,该接口实际上是 JRuby 的整个内部结构,类似于 MRI C 扩展接口的工作方式。
TruffleRuby 目前不支持编写这种类型的 Java 扩展。我们建议您使用上述 Java 互操作性。
工具 #
独立类和 JAR #
JRuby 支持使用 jrubyc
从 Ruby 编译成独立的源代码类和已编译的 JAR。
TruffleRuby 不支持将 Ruby 代码编译为 Java。我们建议您使用 Polyglot API 作为从 Java 到 Ruby 的入口点。
Warbler #
JRuby 支持构建 WAR 文件以加载到企业 Java Web 服务器中。
TruffleRuby 目前不支持此操作。
VisualVM #
VisualVM 适用于 TruffleRuby,与 JRuby 相同。
此外,当您使用堆转储工具时,VisualVM 会理解 Ruby 对象,而不是 Java 对象。