The Spring-Ruby connection - No Fluff Just Stuff

The Spring-Ruby connection

Posted by: Craig Walls on March 24, 2006

Back in August, I blogged about a little experiment in which I used JRuby to access a Spring application context from a Ruby script. That was a lot of fun and brought together two of my favorite things: Spring and Ruby. Today, I turn the tables on that experiment and show you how to wire a Ruby class into your Spring objects.

Spring 2.0 M2 introduced a new set of script factory beans that enable you to load scripted objects into the Spring application context and to manipulate them as if they were written in compiled Java.

To get started, you're going to need spring.jar from Spring 2.0-M2 (or greater) in your classpath. You'll also need commons-logging.jar and jruby.jar.

Next, let's define a Ruby class. For fun, I'll revisit the lime-in-the-coconut example that I used back in August. This time, however, the lime will be a Ruby class:

class Lime
  def drink
    puts "Call the doctor, wake him up!"
  end
end
Lime.new

Nothing too exciting here...just a simple Ruby class. Now we're going to need a Java interface so that the Java side knows how to talk to the Ruby class:

package com.habuma.scripting;

public interface Lime {
  public void drink();
}

Again, nothing too fancy here. This is just an ordinary Java interface. We're going to put it in front of the Ruby class, but it could just as easily be implemented in Java.

Now let's give the lime something to do. Here's a Coconut POJO that we'll inject a Lime into:

package com.habuma.scripting;

public class Coconut {
  public Coconut() {
    System.out.println("You put the lime in the coconut...");
  }
  
  public void drinkThemBothUp() {
    lime.drink();
  }
  
  private Lime lime;
  public void setLime(Lime lime) {
    this.lime = lime;
  }
}

Now we have a Lime interface, a Coconut that is injected with the Lime, and a Ruby class that implements the Lime. With all of the key pieces in place, now let's wire them up in Spring. First, we'll declare the "lime" and the "coconut" beans:

  <bean id="lime"
      class="org.springframework.scripting.jruby.JRubyScriptFactory">
    <constructor-arg value="file:myscripts/lime.rb" />
    <constructor-arg value="com.habuma.scripting.Lime" />
  </bean>

  <bean id="coconut"
      class="com.habuma.scripting.Coconut">
    <property name="lime" ref="lime" />
  </bean>

The "coconut" bean is straight-forward. But the "lime" bean is a bit more interesting. The magic here is the JRubyScriptFactory. It's purpose in life is to load a Ruby script using JRuby and make it available as an object to be wired in Spring. The first constructor-arg is the location of the Ruby script (here designated to be in the filesystem). The second constructor-arg is the fully-qualified class name of the interface that the Ruby script will implement.

All of this is good, but there's one last thing to be done. We must also declare a ScriptFactoryPostProcessor:

  <bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor" />

ScriptFactoryPostProcessor examines the application context looking for script factory beans. If it finds one, it replaces the actual factory bean with the script implementation that the script factory bean loads.

Now you're ready to retrieve the "coconut" bean and use it:

    ApplicationContext ctx =
      new ClassPathXmlApplicationContext("com/habuma/scripting/lime-coconut.xml");
    
    Coconut coconut = (Coconut) ctx.getBean("coconut");
    coconut.drinkThemBothUp();

One other interesting (and quite useful) thing you can do is set the "refreshCheckDelay" property on ScriptFactoryPostProcessor:

  <bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor">
    <property name="refreshCheckDelay" value="5" />
  </bean>

This property tells the ScriptFactoryPostProcessor to keep an eye out for changes in the script file and reload it if it changes. This way you can dynamically change the behavior of your Spring application by simply changing the code in the scripted file. In this case, we're telling ScriptFactoryPostProcessor to poll for updates every 5 seconds.

I've shown you how to use JRubyScriptFactory, but you may be interested to know that Spring 2.0 will also come with GroovyScriptFactory and BshScriptFactory. So, if Ruby's not your scripting language of choice, then you can choose to use either Groovy or Bsh.

For example, here's how you'd define the lime object in Groovy:

class Lime implements com.habuma.scripting.Lime {
  void drink() {
    println "Call the doctor, wake him up!"
  }
}

Then you would wire the "lime" bean as follows:

  <bean id="lime"
      class="org.springframework.scripting.groovy.GroovyScriptFactory">
    <constructor-arg value="file:myscripts/lime.groovy" />
  </bean>

The second constructor argument is gone. There's no need to specify the interface that the Groovy class implements, because it's explicitly implemented in the Groovy script.

(Note: For Groovy scripting, I had to add a few more items to the classpath--groovy-1.0-jsr-04.jar, asm-2.2.1.jar, and antlr-2.7.6rc1.jar--all of which are available in the Spring distribution's lib directory.)

For those of you who may be fans of Bsh, here's the Bsh version:

void drink() {
  System.out.println("Call the doctor, wake him up!");
}

The big difference here is that you don't need to define a class...just a drink() method that implements the requirements of the Lime interface.

Along with the BSH "lime" bean declaration:

  <bean id="lime"
      class="org.springframework.scripting.bsh.BshScriptFactory">
    <constructor-arg value="classpath:com/habuma/scripting/lime.bsh" />
    <constructor-arg value="com.habuma.scripting.Lime" />
  </bean>

(Note: To make the Bsh version work, I had to add bsh-2.0b4.jar to the classpath. Again, this comes with the Spring distribution.)

Running scripted languages within the JVM has long been a handy way to dynamically drive the functionality of Java applications without requiring a recompile and redeploy. Now, as I've shown you, Spring 2.0 brings JVM scripting into the Spring application context, enabling you to define "beans" that are actually defined in scripted languages like Ruby and Groovy.

Sorry, JavaScript and Python fans. I see no hint of any plans to add Rhino and Jython script factories to Spring.

Craig Walls

About Craig Walls

Craig Walls is a Principal Engineer, Java Champion, Alexa Champion, and the author of Spring AI in Action, Spring in Action, and Build Talking Apps. He's a zealous promoter of the Spring Framework, speaking frequently at local user groups and conferences and writing about Spring. When he's not slinging code, Craig is planning his next trip to Disney World or Disneyland and spending as much time as he can with his wife, two daughters, 1 bird and 2 dogs.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »