Chapter 4. Scripting in Spring

In this chapter, you will learn how to use scripting languages in Spring applications. Spring supports three different scripting languages: JRuby, Groovy, and BeanShell. They are the most popular scripting languages in the Java community, and most Java developers find these languages easy to learn.

JRuby (http://jruby.codehaus.org/) is an open source Java-based implementation of the popular Ruby programming language (http://www.ruby-lang.org/). JRuby supports two-way access between Java and Ruby, which means that you can call a Ruby script directly from a Java program and also access Java classes in a Ruby script.

Groovy (http://groovy.codehaus.org/) is a dynamic language for the Java platform that integrates the features of other excellent programming languages. It can be compiled directly into Java bytecode or used as a dynamic scripting language. The syntax of Groovy is very similar to Java, so Java developers can learn Groovy quickly. Moreover, you can access all Java classes and libraries in Groovy.

BeanShell (http://www.beanshell.org/) is a lightweight Java scripting language that can dynamically execute Java code fragments while supporting scripting features like those of other scripting languages. With BeanShell, you can simply script a dynamic module of your Java application without having to learn a new language.

After finishing this chapter, you will be able to script parts of your Spring application using these scripting languages.

Implementing Beans with Scripting Languages

Problem

Sometimes, your application may have certain modules that require frequent and dynamic changes. If implementing these modules with Java, you have to recompile, repackage, and redeploy your application each time after the change. You may not be allowed to perform these actions at any time you want or need, especially for a 24/7 application.

Solution

You can consider implementing any modules that require frequent and dynamic changes with scripting languages. The advantage of scripting languages is that they don't need to be recompiled after changes, so you can simply deploy the new script for the change to take effect.

Spring allows you to implement a bean with one of its supported scripting languages. You can configure a scripted bean in the IoC container just like a normal bean implemented with Java.

How It Works

Suppose you are going to develop an application that requires interest calculation. First of all, you define the following InterestCalculator interface:

package com.apress.springrecipes.interest;

public interface InterestCalculator {

    public void setRate(double rate);
    public double calculate(double amount, double year);
}

Implementing this interface is not difficult at all. However, as there are many interest calculation strategies, users may need to change the implementation very frequently and dynamically. You don't want to recompile, repackage, and redeploy your application every time this happens. So, you consider implementing this interface with one of the supported scripting languages in Spring.

In Spring's bean configuration file, you have to include the lang schema definition in the <beans> root element to make use of the scripting language support.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang-3.0.xsd">
    ...
</beans>

Spring 2.5 supports three scripting languages: JRuby, Groovy, and BeanShell. Next, you will implement the InterestCalculator interface with these languages one by one. For simplicity's sake, let's consider the following simple interest formula for interest calculation:

Interest = Amount x Rate x Year

Scripting Beans with JRuby

First, let's implement the InterestCalculator interface with JRuby by creating the JRuby script, SimpleInterestCalculator.rb, in the com.apress.springrecipes.interest package of your classpath.

class SimpleInterestCalculator

    def setRate(rate)
        @rate = rate
    end

    def calculate(amount, year)
        amount * year * @rate
    end
end

SimpleInterestCalculator.new

The preceding JRuby script declares a SimpleInterestCalculator class with a setter method for the rate property and a calculate() method. In Ruby, an instance variable begins with the @ sign. Note that, in the last line, you return a new instance of your target JRuby class. Failure to return this instance may result in Spring performing a lookup for an appropriate Ruby class to instantiate. As there can be multiple classes defined in a single JRuby script file, Spring will throw an exception if it cannot find an appropriate one that implements the methods declared in the interface.

In the bean configuration file, you can declare a bean implemented with JRuby by using the <lang:jruby> element and specifying the script's location in the script-source attribute. You can specify any resource path with a resource prefix supported by Spring, such as file or classpath.

Note

To use JRuby in your Spring application, you have to include the appropriate dependencies on your classpath. If you are using Maven, then add the following definition to your Maven project:

<dependency>
  <groupId>org.jruby</groupId>
  <artifactId>jruby</artifactId>
  <version>1.0</version>
 </dependency>
<lang:jruby id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/
Scripting Beans with JRuby
SimpleInterestCalculator.rb" script-interfaces="com.apress.springrecipes.interest.InterestCalculator"> <lang:property name="rate" value="0.05" /> </lang:jruby>

You also have to specify one or more interfaces in the script-interfaces attribute for a JRuby bean. It's up to Spring to create a dynamic proxy for this bean and convert the Java method calls into JRuby method calls. Finally, you can specify the property values for a scripting bean in the <lang:property> elements.

Now, you can get the interestCalculator bean from the IoC container to use and inject it into other bean properties as well. The following Main class will help you verify whether your scripted bean works properly:

package com.apress.springrecipes.interest;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) throws Exception {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans.xml");

        InterestCalculator calculator =
            (InterestCalculator) context.getBean("interestCalculator");
        System.out.println(calculator.calculate(100000, 1));
    }
}

Scripting Beans with Groovy

Next, let's implement the InterestCalculator interface with Groovy by creating the Groovy script, SimpleInterestCalculator.groovy, in the com.apress.springrecipes.interest package of your classpath.

import com.apress.springrecipes.interest.InterestCalculator;

class SimpleInterestCalculator implements InterestCalculator {

    double rate

    double calculate(double amount, double year) {
        return amount * year * rate
    }
}

The preceding Groovy script declares a SimpleInterestCalculator class that implements the InterestCalculator interface. In Groovy, you can simply declare a property with no access modifier, and then it will generate a private field with a public getter and setter automatically.

In the bean configuration file, you can declare a bean implemented with Groovy by using the <lang:groovy> element and specifying the script's location in the script-source attribute. You can specify the property values for a scripting bean in the <lang:property> elements.

Note

To use Groovy in your Spring application, you need to add the dependency to the classpath. If you are using Maven, add the following dependency to your Maven project:

<dependency>
  <groupId>org.jruby</groupId>
  <artifactId>jruby</artifactId>
  <version>1.0</version>
 </dependency>
<lang:groovy id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/SimpleInterestCalculator.groovy">
    <lang:property name="rate" value="0.05" />
</lang:groovy>

Notice that it's unnecessary to specify the script-interfaces attribute for a Groovy bean, as the Groovy class has declared which interfaces it implements.

Scripting Beans with BeanShell

Last, let's implement the InterestCalculator interface with BeanShell by creating the BeanShell script, SimpleInterestCalculator.bsh, in the com.apress.springrecipes.interest package of your classpath.

double rate;

void setRate(double aRate) {
    rate = aRate;
}

double calculate(double amount, double year) {
    return amount * year * rate;
}

In BeanShell, you cannot declare classes explicitly, but you can declare variables and methods. So, you implement your InterestCalculator interface by providing all the methods required by this interface.

In the bean configuration file, you can declare a bean implemented with BeanShell by using the <lang:bsh> element and specifying the script's location in the script-source attribute. You can specify the property values for a scripting bean in the <lang:property> elements.

Note

To use BeanShell Spring application, you need to add the dependency to the classpath. If you are using Maven, add the following dependency to your Maven project:

<dependency>
  <groupId>org.beanshell</groupId>
  <artifactId>bsh</artifactId>
  <version>2.0b4</version>
 </dependency>
<lang:bsh id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/SimpleInterestCalculator.bsh"
    script-interfaces="com.apress.springrecipes.interest.InterestCalculator">
    <lang:property name="rate" value="0.05" />
</lang:bsh>

You also have to specify one or more interfaces in the script-interfaces attribute for a bean implemented with BeanShell. It's up to Spring to create a dynamic proxy for this bean and convert the Java method calls into BeanShell calls.

Injecting Spring Beans into Scripts

Problem

Sometimes, your scripts may need the help of certain Java objects to complete their tasks. In Spring, you have to allow your scripts to access beans declared in the IoC container.

Solution

You can inject beans declared in the Spring IoC container into scripts in the same way as properties of simple data types.

How It Works

Suppose you would like the interest rate to be calculated dynamically. First, you define the following interface to allow implementations to return the annual, monthly, and daily interest rates:

package com.apress.springrecipes.interest;

public interface RateCalculator {

    public double getAnnualRate();
    public double getMonthlyRate();
    public double getDailyRate();
}

For this example, you simply implement this interface by calculating these rates from a fixed annual interest rate, which can be injected through a setter method.

package com.apress.springrecipes.interest;

public class FixedRateCalculator implements RateCalculator {

    private double rate;

    public void setRate(double rate) {
        this.rate = rate;
    }

    public double getAnnualRate() {
        return rate;
    }

    public double getMonthlyRate() {
        return rate / 12;
    }

    public double getDailyRate() {
        return rate / 365;
    }
}

Then declare this rate calculator in the IoC container by supplying an annual interest rate.

<bean id="rateCalculator"
    class="com.apress.springrecipes.interest.FixedRateCalculator">
    <property name="rate" value="0.05" />
</bean>

Last, your interest calculator should use a RateCalculator object rather than a fixed rate value.

package com.apress.springrecipes.interest;

public interface InterestCalculator {

    public void setRateCalculator(RateCalculator rateCalculator);
    public double calculate(double amount, double year);
}

Injecting Spring Beans into JRuby

In the JRuby script, you can store the injected RateCalculator object in an instance variable and use it for rate calculation.

class SimpleInterestCalculator

    def setRateCalculator(rateCalculator)
        @rateCalculator = rateCalculator
    end

    def calculate(amount, year)
        amount * year * @rateCalculator.getAnnualRate
    end
end

SimpleInterestCalculator.new

In the bean declaration, you can inject another bean into a scripted bean's property by specifying the bean name in the ref attribute.

<lang:jruby id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/
Injecting Spring Beans into JRuby
SimpleInterestCalculator.rb" script-interfaces="com.apress.springrecipes.interest.InterestCalculator"> <lang:property name="rateCalculator" ref="rateCalculator" /> </lang:jruby>

Injecting Spring Beans into Groovy

In the Groovy script, you just declare a property of type RateCalculator, and it will generate a public getter and setter automatically.

import com.apress.springrecipes.interest.InterestCalculator;
import com.apress.springrecipes.interest.RateCalculator;

class SimpleInterestCalculator implements InterestCalculator {

    RateCalculator rateCalculator

    double calculate(double amount, double year) {
        return amount * year * rateCalculator.getAnnualRate()
    }
}

Again, you can inject another bean into a scripted bean's property by specifying the bean name in the ref attribute.

<lang:groovy id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/SimpleInterestCalculator.groovy">
    <lang:property name="rateCalculator" ref="rateCalculator" />
</lang:groovy>

Injecting Spring Beans into BeanShell

In the BeanShell script, you need a global variable of type RateCalculator and a setter method for it.

import com.apress.springrecipes.interest.RateCalculator;

RateCalculator rateCalculator;

void setRateCalculator(RateCalculator aRateCalculator) {
    rateCalculator = aRateCalculator;
}

double calculate(double amount, double year) {
    return amount * year * rateCalculator.getAnnualRate();
}

Also, you can inject another bean into a scripted bean's property by specifying the bean name in the ref attribute.

<lang:bsh id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/
Injecting Spring Beans into BeanShell
SimpleInterestCalculator.bsh" script-interfaces="com.apress.springrecipes.interest.InterestCalculator"> <lang:property name="rateCalculator" ref="rateCalculator" /> </lang:bsh>

Refreshing Beans from Scripts

Problem

As the modules implemented with scripting languages may have to be changed frequently and dynamically, you would like the Spring IoC container to be able to detect and refresh changes automatically from the script sources.

Solution

Spring is able to refresh a scripted bean definition from its source once you have specified the checking interval in the refresh-check-delay attribute. When a method is called on that bean, Spring will check the script source if the specified checking interval has elapsed. Then, Spring will refresh the bean definition from the script source if it has been changed.

How It Works

By default, the refresh-check-delay attribute is negative, so the refresh checking feature is disabled. You can assign the milliseconds for refresh checking in this attribute to enable this feature. For example, you can specify 5 seconds for the refresh checking interval of your JRuby bean.

<lang:jruby id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/
How It Works
SimpleInterestCalculator.rb" script-interfaces="com.apress.springrecipes.interest.InterestCalculator" refresh-check-delay="5000"> ... </lang:jruby>

Of course, the refresh-check-delay attribute also works for a bean implemented with Groovy or BeanShell.

<lang:groovy id="interestCalculator"
    script-source="classpath:com/apress/springrecipes/interest/
How It Works
SimpleInterestCalculator.groovy" refresh-check-delay="5000"> ... </lang:groovy> <lang:bsh id="interestCalculator" script-source="classpath:com/apress/springrecipes/interest/
How It Works
SimpleInterestCalculator.bsh" script-interfaces="com.apress.springrecipes.interest.InterestCalculator" refresh-check-delay="5000"> ... </lang:bsh>

Defining Script Sources Inline

Problem

You would like to define the script sources, which are not likely to be changed often, in the bean configuration file directly, rather than in external script source files.

Solution

You can define an inline script source in the <lang:inline-script> element of a scripted bean to replace a reference to an external script source file by the script-source attribute. Note that the refresh checking feature is not applicable for an inline script source, because the Spring IoC container only loads the bean configuration once, at startup.

How It Works

For example, you can define the JRuby script inline using the <lang:inline-script> element. To prevent the characters in your script from conflicting with the reserved XML characters, you should surround your script source with the <![CDATA[...]]> tag. You no longer have to specify the reference to the external script source file in the script-source attribute.

<lang:jruby id="interestCalculator"
    script-interfaces="com.apress.springrecipes.interest.InterestCalculator">
    <lang:inline-script>
    <![CDATA[
    class SimpleInterestCalculator

        def setRateCalculator(rateCalculator)
            @rateCalculator = rateCalculator
        end

        def calculate(amount, year)
            amount * year * @rateCalculator.getAnnualRate
        end
    end

    SimpleInterestCalculator.new
    ]]>
    </lang:inline-script>
    <lang:property name="rateCalculator" ref="rateCalculator" />
</lang:jruby>

Of course, you can also define the Groovy or BeanShell script sources inline using the <lang:inline-script> element.

<lang:groovy id="interestCalculator">
    <lang:inline-script>
    <![CDATA[
    import com.apress.springrecipes.interest.InterestCalculator;
    import com.apress.springrecipes.interest.RateCalculator;

    class SimpleInterestCalculator implements InterestCalculator {

        RateCalculator rateCalculator

        double calculate(double amount, double year) {
            return amount * year * rateCalculator.getAnnualRate()
        }
    }
    ]]>
    </lang:inline-script>
    <lang:property name="rateCalculator" ref="rateCalculator" />
</lang:groovy>
<lang:bsh id="interestCalculator"
    script-interfaces="com.apress.springrecipes.interest.InterestCalculator">
    <lang:inline-script>
    <![CDATA[
    import com.apress.springrecipes.interest.RateCalculator;

    RateCalculator rateCalculator;

    void setRateCalculator(RateCalculator aRateCalculator) {
        rateCalculator = aRateCalculator;
    }

    double calculate(double amount, double year) {
        return amount * year * rateCalculator.getAnnualRate();
    }
    ]]>
    </lang:inline-script>
    <lang:property name="rateCalculator" ref="rateCalculator" />
</lang:bsh>

Summary

In this chapter, you have learned how to use the scripting languages supported by Spring to implement your beans and how to declare them in the Spring IoC container. Spring supports three scripting languages: JRuby, Groovy, and BeanShell. You can specify the location for an external script source file or define an inline script source in the bean configuration file. As the script sources may require frequent and dynamic changes, Spring can detect and refresh changes from the script source files automatically. Finally, you can inject property values as well as bean references into your scripts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset