Experimental feature in GraalVM

從 JRuby 遷移至 TruffleRuby

當您在您的 gem 和應用程式上嘗試 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。

請參閱多語言文件,以取得有關如何從其他語言(包括 Java)使用 Ruby 的更多資訊;此文件僅顯示與 JRuby 的比較。

建立 Context #

在 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 區塊中建立您的 Context,以確保它被正確處置

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 中,您需要要求 context 叫用該方法,或者在直接嵌入的情況下,您需要在接收者上呼叫方法,並將引數編組為您自己的 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 中,您可以使用 getArrayElementsetArrayElementgetArraySize,或者您可以使用 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 模式下執行。這在原生獨立版中不可用。

參考類別 #

在 JRuby 中,Java 類別可以在 Java 模組中參考,例如 Java::ComFoo::Bar,或者如果它們具有通用 TLD,則可以參考為 com.foo.Barjava_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 中,您呼叫 Java 方法的方式與呼叫 Ruby 方法相同。

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.FOOMyClass[: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 物件。

與我們聯繫