trav.chung
trav.chung

Reputation: 175

Calling Ruby method from Java

I have a Java application that depends on the result from a ruby script. The issue I'm facing is that this ruby script is called many times which requires loading the script's libraries every time it's executed.

I'm not familiar with Ruby, but in my search I've come across JRuby. Since it interprets ruby code and runs it on the JVM, my initial thought was "great, I can just compile the ruby script to .class files package it up and interact with it like regular java objects". This would avoid the overhead that came with loading the libraries each time I executed the ruby script.

After looking deeper into jruby, I'm understanding it doesn't work this way. So to do what I want I can use JRuby's JavaEmbedUtils to

  1. Load a Ruby runtime object with my ruby scripts
  2. Create a receiver object that essentially knows how to handle and respond to my script.
  3. Invoke the desired method from script.

This is what I had in mind (I'll be testing soon)

// 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);

Is my understanding correct in that this approach allows me to use the contents of the script many times while loading the script's libraries once? Is there an alternate or more JRuby way of doing what i'm looking for?

UPDATE

So I tested something similar to what Eugene suggested (a) i.e. ScriptingContainer and compared that to calling the script using (b) java.lang.Runtime for a total of 30 runs.

  1. On average, (a) was about 8x faster (b)
  2. Initializing (a)'s runtime took about 7 seconds
  3. The initial run for both (a) & (b) took 40x & 150-250x longer than following runs.
  4. (a)'s initial run was 11x faster than (b)s

Upvotes: 2

Views: 2957

Answers (1)

en1010
en1010

Reputation: 1300

This is how I use JRuby in a scripting container from java code:

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);

I have to note that JRuby is too slow on launch (these numbers is not too precise but in my case it is 2-3 sec. even on 4Ghz + SSD). TDD turns into pain with such delays. That's why there are some tweaks as object space, etc.

Also, I had to stub out this module so the rest of test uses other fixtures and run without launching JRuby. Other words, I don't have to launch it while running another test separately.

P.S.

Is my understanding correct in that this approach requires me to only load the script's dependencies once?

I'm not sure what do you mean here, but it seems all the dependencies "required" on JRuby side will be loaded every time while using this approach.

Anyway, I would make a benchmark before. Maybe it isn't worth to be thought about.

UPD:

This was more simple than I thought:

common.rb:

puts "common"

script.rb:

require 'common'

puts "script"

code:

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

Output:

common
script
script
script

Upvotes: 3

Related Questions