As of Spring 1.3, you can use a variety of languages, including Groovy and Beanshell, to implement any object managed by a Spring container. As other objects depend on that object by interface, they are unaware of its implementation language — another example of the benefits of the pluggability that an IoC container provides. You can configure a scripted object using Spring Dependency Injection also, using Setter Injection. (Property support will look different in different languages, but is usually nicer than in Java.) Scripted objects can have simply properties, but depend on other objects written in Java or any other scripting language.
You can also apply the full power of Spring's AOP framework to any scripted object to apply out-of-the-box aspects, such as declarative transaction management, or custom aspects. This is a capability that is possible only with a proxy-based AOP technology such as Spring AOP. Because the target object does not need to be modified, it doesn't matter what language the original object is written in. AspectJ or AspectWerkz, on the other hand, could not weave against a non-Java object — although it might be possible if it directly produced byte code, as in the case of Groovy.
Whatever the scripting language, the support is consistent. Bean definition isolates the non-Java elements of an application.
You need the following bean definitions:
A script factory for each language you want to use: This is where you specify properties affecting all scripts, such as whether scripts should auto refresh — that is, poll the definition file to check whether it has changed. (You don't normally need to specify any properties.) One script factory can be used for many scripts.
A bean definition for each bean that is implemented in that language: The definition can be inline, or can specify a resource, using Spring's resource abstraction.
Spring does a lot of work behind the scenes to support scripted objects. The support is largely accomplished using Spring AOP. The AOP infrastructure introduces the level of indirection necessary to support reloading of scripted objects without affecting callers and also introduces a special interface that is implemented by all scripted objects: org.springframework.beans.factory.script.DynamicScript. This defines methods returning the interface or interfaces supported by the script; the resource location of the script; how many times the script has been reloaded; and a refresh() method allowing forcible reloading.
Why implement objects in a language other than Java? This decision is yours: Spring merely gives you a choice.
In general, you might choose to implement an object in a scripting language if:
— Doing so makes the code in the object significantly simpler — for example, because of language features such as closures, or because you are not forced to implement methods that can never be called.
— You want to be able to modify the script while your application is running, thus changing its behavior without affecting objects that depend on the script.
— Performance is not critical. Most scripting languages do impose some performance penalty, even if they can be compiled to byte code.
You can cast any scripted object to DynamicScript to access these methods.
Note | The DynamicScript interface extends the generic org.springframework.beans.factory.dynamic.DynamicObject interface, from which it inherits the refresh() method and other important methods. Future versions of Spring will use this interface to support a wider range of"dynamic objects," such as objects whose bean definition is sourced from a database and may change without impacting callers. |
Spring supports both singleton and prototype lifecycles for scripted objects.
It's possible to declare scripts inline, in the XML or other configuration, by prefixing the script location argument (to be discussed shortly) with inline. While you might want to use this as a convenience during rapid prototyping, in general, you should not program in configuration documents. If you declare scripts inline, your gain in convenience will also be offset by the loss of the dynamic reload capability; the script definition is now tied to that of the overall configuration.
One legitimate use for inline declarations — and an important reason for using scripting in some cases in general — is when you don't want to implement all the methods on an interface. In this case, using a scripting language such as Beanshell, you can have Spring cast the script object to one or more interfaces, and the methods you don't want implemented will throw UnsupportedOperationException if they are ever called. Especially when prototyping, this may be exactly what you want.
Let's look at how we could use a Groovy script in a Spring application.
We'll take our example implementations of the following simple interface:
public interface Hello { String sayHello(); }
We will use Setter Injection to parameterize the message returned by implementations, demonstrating how Dependency Injection can be applied to scripted objects.
First we need to define a Groovy Script factory. This will be responsible for instantiating any Groovy scripts we need to use. This can be as simple as the following, which merely specifies the class:
<bean > </bean>
This bean definition can have any name. However, that name will need to be referenced whenever we create a new script object:
Next we must make a bean definition for each Groovy-scripted object:
<bean factory-bean="groovyScriptFactory" factory-method="create" > <constructor-arg index="0"><value>org/springframework/beans/factory/script/groovy/PropertyHello.groo vy</value></constructor-arg> <property name="message"><value>hello world property</value></property> </bean>
This uses the factory method feature of the Spring IoC container. The constructor argument is the location argument to the create() method of the instance of GroovyScriptFactory defined in the groovy ScriptFactory bean definition. The create() method is defined on AbstractScriptFactory, the super- class for all script factories, so we will consistently use the same method naming whatever the language, and script factory class, we use.
Properties defined in this bean definition, on the other hand, apply to the created Groovy script. We merely use the factory to instantiate scripts; we are free to configure them using Setter Injection.
Other beans can depend on this script object using Dependency Injection, and the script object can also depend on other objects using Dependency Injection. In this case, the message property is simply a String, but we could equally use references as in any regular bean definition.
We don't need to specify the interfaces that this script implements. As Groovy can define Java classes, Spring will use CGLIB proxying to ensure that all methods implemented by the Groovy object will be available. Thus all interfaces it implements will be available: It is the Groovy code itself that is responsible for declaring what interfaces it implements, and hence how it can be used by callers.
Let's look at the Groovy script itself:
package org.springframework.beans.factory.script.groovy class Test implements org.springframework.beans.factory.script.Hello { property message String sayHello() { message } }
As you can see, Groovy's economic property syntax makes this more concise than the Java equivalent.
Beanshell is a very different language to Groovy. It is interpreted, rather than compiled to byte code, and Bean- shell provides only dynamic proxies that implement a single interface. The use of dynamic proxies means that class methods are not an option, as in Groovy. Thus we need a different approach to Setter Injection.
Nevertheless, Spring support is very consistent: It allows scripts to implement multiple interfaces (internally, calling several Beanshell evaluations); and it supports property configuration, with a little magic of its own.
We create a Beanshell script factory as follows:
<bean > </bean>
Individual bean definitions will look like this:
<bean factory-bean="scriptFactory" factory-method="create" > <constructor-arg index="0"><value>classpath:/org/springframework/beans/factory/script/bsh/SimpleHell o.bsh</value></constructor-arg> <constructor-arg index="1"><value>org.springframework.beans.factory.script.Hello</value></constructo r-arg> <property name="message"><value>hello world property</value></property> </bean>
All this looks similar to Spring's Groovy support, enabling a consistent configuration model. However, it works quite differently under the covers.
Note that we must specify a second argument to the create() method on BshScriptFactory, defining the interface or interfaces implemented by the Script. (This is actually a String array, so we can specify multiple interfaces. But most often we'll want only one.) This argument is optional with Groovy, but mandatory with Beanshell, as Spring has to know up front what interfaces it needs to ask Beanshell to create a dynamic proxy for.
But the real magic happens in the handling of setter methods. These are not part of the interface or interfaces to be exposed by the bean definition, and it would be unreasonable to expect the developer to create a "configuration interface" containing these setter methods. Thus Spring itself creates a configuration interface containing the necessary setter methods, by first looking at the bean definition. Thus in the preceding example, Spring will create an interface containing a setMessage() method. Thus the core Spring IoC container is tricked into thinking that the script really implements this interface, and can invoke the setters normally. The actual interface generation is done using a handling "interface maker" provided by CGLIB.
Note | The requiresConfigInterface() method in AbstractScriptFactory must be implemented to return true for languages such as Beanshell. |
The developer is merely responsible for ensuring that Beanshell scripts implement all setter methods implied by the bean definition, not for implementing them in any interface.
Let's look at the Beanshell definition itself. Note how the setMessage() method is implemented:
String message; public void setMessage(String pmessage) { message = pmessage; } String sayHello() { return message; }
Note that this doesn't look like a complete class: We have implemented only the methods we are interested in. In this case, it was the complete Hello interface, but in some cases we won't bother to implement all methods. Nor does the script need to declare what interfaces it implements. The developer is responsible for correct configuration, ensuring that the script isn't specified to implement interfaces for which it doesn't support the methods.
Spring is likely to support additional scripting languages in the future.
It's also possible to implement support for nearly any other scripting language with Java bindings. You need to do the following:
Implement the Script interface for your chosen language. The AbstractScript class is a useful base class.
Extend AbstractScriptFactory to implement a script factory that creates scripts of that type.
While some of the concepts involved are complex, and you should ensure that you thoroughly understand the implementation of Groovy and Beanshell support before trying to implement support for your own favorite language, there is not normally a large amount of code involved.
The Spring scripting infrastructure will automatically introduce the DynamicScript interface for any scripting language supported this way; you don't need to add any explicit support.