Java provides a standardized way to integrate with other programming languages, the so-called JS223 scripting standard. This standard centers around the interface ScriptEngine.
While you can work on the ScriptEngine
level directly, it is
more convenient to use the Java class
ScriptingContainer
,
which is part of the JRuby API. Basically, a ScriptingContainer
object
let's you execute Ruby source code. The value of this execution - remember, that
every block of Ruby code returns some value! - can be retrieved in Java, and all
global variables allocated during the execution, persist and are available on the
Java side as well.
In the simplest way, the scripting container executes just a single Ruby expression: The allocation of some Ruby object, which then is known on the Java side, and which can be used to passing information forth and back.
# Ruby - the simplest (useful) script executed by the scripting container require 'myrubydir/myapplication' # Load class definition MyApplication.new # Return it to Java
(This assumes, that we will include a file
myrubydir/myapplication.rb
into our jar file)
Of course nobody keeps you from more creating more elaborate interactions, and the example presented here, returns not only one object, but also creates a global Ruby valuable, which then is accessed on the Java side.
While the class ScriptingContainer
also provides a default constructor, we
will in general use the constructor with the signature
ScriptingContainer(LocalContextScope)
.
The LocalContextScope parameter is an enumeration type and determines the
behaviour in a multithreading environment, as explained in the next chapter.
If the application runs from a Jar file *and* all gems are inside the Jar
file, it is advisable to use an
IsolatedScriptingContainer
instead of a ScriptingContainer
. This is a plugin replacement,
which sets GEM_HOME
and GEM_PATH
in a suitable way.
See the chapter on gems for more details.
When we start executing a Ruby program - and this applies to Ruby in general, not
just JRuby -, we have a top-level context. This is
represented by an object of class Object
, and if we, for instance,
define a Ruby function, this function becomes a member function of this top-level
object. This can easiliy seen by running the single-line program
puts self.class # Ruby
which outputs Object.
Now to JRuby: A ScriptingContainer contains a Ruby Runtime, and a Ruby Runtime contains a Variable Map. These things are explained in the following:
A ScriptingContainer
comprises of the full
implementation of the Ruby language - compiler, runtime, standard library, all
standard gems which are inclued in the distribution. This is necessary, because
we don't know what features will be used, before executing our program.
An application can have more than one containers, for example each one containing
a different Ruby version.
The Ruby Runtime - in Java implemented as object of type
org.jruby.Ruby
contains th top level Ruby object, which I have mentioned earlier. For example,
Ruby global variables are stored here.
JRuby also offers the possibility to share local variables between the Ruby and the Java world, and use Java objects on the Ruby side. For example, you can create a variable on some Java class type on the Java side, pass it to Ruby (via a method of some Ruby object) and work with this Java-object on the Ruby side. This is done by storing these bindings in a Variable Map, implemented in Java as object of type org.jruby.embed.internal.BiVariableMap.
JRuby gives the user some control over the extent of sharing, respectively replicating the container, the runtime, and the variable map. For example, if the application is configured to share the ScriptingContainer, the container will actually become a singleton, and each allocation of the container will in fact return the same object. The policy of sharing is encoded in the enumeration type LocalContextScope, and 4 sharing models are currently offered:
scope | enum name | meaning |
---|---|---|
singleton | LocalContextScope.SINGLETON | We can have many ScriptingContainer instances, but they
share a common Runtime and
Variable Map. The containers are thread-agnostic.
|
threadsafe | LocalContextScope.THREADSAFE | We have a single ScriptingContainer shared for all threads, but each thread has its own Runtime and Variable Map. |
singlethread | LocalContextScope.SINGLETHREAD | Nothing is shared, but this is still thread-agnostic. If we use the same container in several threads, we have to care explicitly about concurrency. |
concurrent | LocalContextScope.CONCURRENT | The container and the runtime is shared between threads, but each thread has its own variable map. |
There are two ways to establish the local context scope for an application:
ScriptingContainer
.
For your own mental sanity, pass the same value to all of your containers.ScriptingContainer
.Example: Setting the local context scope to concurrent:
System.setProperty("org.jruby.embed.localcontext.scope", "concurrent"); // Java
JRuby lets you choose, whether local variables should really be local (as it is according to Ruby semantics), or whether they should be shared too between Java and Ruby and hence persist between evaluations of scripts. I have yet to see an application where I would really want to have this behaviour, and I strongly prefer that locals behave as the Ruby-God intended, that is: locally. To ensure that this is the case, set the following system property:
System.setProperty("org.jruby.embed.localvariable.behavior", "transient"); // Java
This would be the code for creating our bridge in a concurrent context - our bridge is established on the Ruby side by executing the Ruby script bridge.rb, which in turn creates some global variables (which will be available on the Java side through the scripting container's Ruby Runtime) and returns explicitly an instance of the Ruby class Asahi:
// Java import org.jruby.Ruby; import org.jruby.RubyObject; import org.jruby.embed.ScriptingContainer; import org.jruby.embed.LocalContextScope; .... ScriptingContainer container = new ScriptingContainer(LocalContextScope.CONCURRENT); RubyObject asahi = (RubyObject)container.runScriptlet(org.jruby.embed.PathType.CLASSPATH,"rbsrc/bridge.rb");
Several points are worth mentioning:
RubyObject
. If the returned
value would be a "simple" type, such as a number or a string, we could use
it directly, if we want to. How to access the internals of our asahi
is shown here.