859 lines
33 KiB
Plaintext
859 lines
33 KiB
Plaintext
|
[[dynamic-language]]
|
|||
|
= Dynamic Language Support
|
|||
|
|
|||
|
Spring provides comprehensive support for using classes and objects that have been
|
|||
|
defined by using a dynamic language (such as Groovy) with Spring. This support lets
|
|||
|
you write any number of classes in a supported dynamic language and have the Spring
|
|||
|
container transparently instantiate, configure, and dependency inject the resulting
|
|||
|
objects.
|
|||
|
|
|||
|
Spring's scripting support primarily targets Groovy and BeanShell. Beyond those
|
|||
|
specifically supported languages, the JSR-223 scripting mechanism is supported
|
|||
|
for integration with any JSR-223 capable language provider (as of Spring 4.2),
|
|||
|
e.g. JRuby.
|
|||
|
|
|||
|
You can find fully working examples of where this dynamic language support can be
|
|||
|
immediately useful in <<dynamic-language-scenarios>>.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-a-first-example]]
|
|||
|
== A First Example
|
|||
|
|
|||
|
The bulk of this chapter is concerned with describing the dynamic language support in
|
|||
|
detail. Before diving into all of the ins and outs of the dynamic language support,
|
|||
|
we look at a quick example of a bean defined in a dynamic language. The dynamic
|
|||
|
language for this first bean is Groovy. (The basis of this example was taken from the
|
|||
|
Spring test suite. If you want to see equivalent examples in any of the other
|
|||
|
supported languages, take a look at the source code).
|
|||
|
|
|||
|
The next example shows the `Messenger` interface, which the Groovy bean is going to
|
|||
|
implement. Note that this interface is defined in plain Java. Dependent objects that
|
|||
|
are injected with a reference to the `Messenger` do not know that the underlying
|
|||
|
implementation is a Groovy script. The following listing shows the `Messenger` interface:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting;
|
|||
|
|
|||
|
public interface Messenger {
|
|||
|
|
|||
|
String getMessage();
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The following example defines a class that has a dependency on the `Messenger` interface:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting;
|
|||
|
|
|||
|
public class DefaultBookingService implements BookingService {
|
|||
|
|
|||
|
private Messenger messenger;
|
|||
|
|
|||
|
public void setMessenger(Messenger messenger) {
|
|||
|
this.messenger = messenger;
|
|||
|
}
|
|||
|
|
|||
|
public void processBooking() {
|
|||
|
// use the injected Messenger object...
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The following example implements the `Messenger` interface in Groovy:
|
|||
|
|
|||
|
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
|||
|
----
|
|||
|
package org.springframework.scripting.groovy
|
|||
|
|
|||
|
// Import the Messenger interface (written in Java) that is to be implemented
|
|||
|
import org.springframework.scripting.Messenger
|
|||
|
|
|||
|
// Define the implementation in Groovy in file 'Messenger.groovy'
|
|||
|
class GroovyMessenger implements Messenger {
|
|||
|
|
|||
|
String message
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
[NOTE]
|
|||
|
====
|
|||
|
To use the custom dynamic language tags to define dynamic-language-backed beans, you
|
|||
|
need to have the XML Schema preamble at the top of your Spring XML configuration file.
|
|||
|
You also need to use a Spring `ApplicationContext` implementation as your IoC
|
|||
|
container. Using the dynamic-language-backed beans with a plain `BeanFactory`
|
|||
|
implementation is supported, but you have to manage the plumbing of the Spring internals
|
|||
|
to do so.
|
|||
|
|
|||
|
For more information on schema-based configuration, see <<xsd-schemas-lang,
|
|||
|
XML Schema-based Configuration>>.
|
|||
|
====
|
|||
|
|
|||
|
Finally, the following example shows the bean definitions that effect the injection of the
|
|||
|
Groovy-defined `Messenger` implementation into an instance of the
|
|||
|
`DefaultBookingService` class:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<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 https://www.springframework.org/schema/beans/spring-beans.xsd
|
|||
|
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
|||
|
|
|||
|
<!-- this is the bean definition for the Groovy-backed Messenger implementation -->
|
|||
|
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
|
|||
|
<lang:property name="message" value="I Can Do The Frug" />
|
|||
|
</lang:groovy>
|
|||
|
|
|||
|
<!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger -->
|
|||
|
<bean id="bookingService" class="x.y.DefaultBookingService">
|
|||
|
<property name="messenger" ref="messenger" />
|
|||
|
</bean>
|
|||
|
|
|||
|
</beans>
|
|||
|
----
|
|||
|
|
|||
|
The `bookingService` bean (a `DefaultBookingService`) can now use its private `messenger`
|
|||
|
member variable as normal, because the `Messenger` instance that was injected into it is
|
|||
|
a `Messenger` instance. There is nothing special going on here -- just plain Java and
|
|||
|
plain Groovy.
|
|||
|
|
|||
|
Hopefully, the preceding XML snippet is self-explanatory, but do not worry unduly if it is not.
|
|||
|
Keep reading for the in-depth detail on the whys and wherefores of the preceding configuration.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans]]
|
|||
|
== Defining Beans that Are Backed by Dynamic Languages
|
|||
|
|
|||
|
This section describes exactly how you define Spring-managed beans in any of the
|
|||
|
supported dynamic languages.
|
|||
|
|
|||
|
Note that this chapter does not attempt to explain the syntax and idioms of the supported
|
|||
|
dynamic languages. For example, if you want to use Groovy to write certain of the classes
|
|||
|
in your application, we assume that you already know Groovy. If you need further details
|
|||
|
about the dynamic languages themselves, see <<dynamic-language-resources>> at the end of
|
|||
|
this chapter.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-concepts]]
|
|||
|
=== Common Concepts
|
|||
|
|
|||
|
The steps involved in using dynamic-language-backed beans are as follows:
|
|||
|
|
|||
|
. Write the test for the dynamic language source code (naturally).
|
|||
|
. Then write the dynamic language source code itself.
|
|||
|
. Define your dynamic-language-backed beans by using the appropriate `<lang:language/>`
|
|||
|
element in the XML configuration (you can define such beans programmatically by
|
|||
|
using the Spring API, although you will have to consult the source code for
|
|||
|
directions on how to do this, as this chapter does not cover this type of advanced configuration).
|
|||
|
Note that this is an iterative step. You need at least one bean definition for each dynamic
|
|||
|
language source file (although multiple bean definitions can reference the same source file).
|
|||
|
|
|||
|
The first two steps (testing and writing your dynamic language source files) are beyond
|
|||
|
the scope of this chapter. See the language specification and reference manual
|
|||
|
for your chosen dynamic language and crack on with developing your dynamic language
|
|||
|
source files. You first want to read the rest of this chapter, though, as
|
|||
|
Spring's dynamic language support does make some (small) assumptions about the contents
|
|||
|
of your dynamic language source files.
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-concepts-xml-language-element]]
|
|||
|
==== The <lang:language/> element
|
|||
|
|
|||
|
The final step in the list in the <<dynamic-language-beans-concepts, preceding section>>
|
|||
|
involves defining dynamic-language-backed bean definitions, one for each bean that you
|
|||
|
want to configure (this is no different from normal JavaBean configuration). However,
|
|||
|
instead of specifying the fully qualified class name of the class that is to be
|
|||
|
instantiated and configured by the container, you can use the `<lang:language/>`
|
|||
|
element to define the dynamic language-backed bean.
|
|||
|
|
|||
|
Each of the supported languages has a corresponding `<lang:language/>` element:
|
|||
|
|
|||
|
* `<lang:groovy/>` (Groovy)
|
|||
|
* `<lang:bsh/>` (BeanShell)
|
|||
|
* `<lang:std/>` (JSR-223, e.g. with JRuby)
|
|||
|
|
|||
|
The exact attributes and child elements that are available for configuration depends on
|
|||
|
exactly which language the bean has been defined in (the language-specific sections
|
|||
|
later in this chapter detail this).
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-refreshable-beans]]
|
|||
|
==== Refreshable Beans
|
|||
|
|
|||
|
One of the (and perhaps the single) most compelling value adds of the dynamic language
|
|||
|
support in Spring is the "`refreshable bean`" feature.
|
|||
|
|
|||
|
A refreshable bean is a dynamic-language-backed bean. With a small amount of
|
|||
|
configuration, a dynamic-language-backed bean can monitor changes in its underlying
|
|||
|
source file resource and then reload itself when the dynamic language source file is
|
|||
|
changed (for example, when you edit and save changes to the file on the file system).
|
|||
|
|
|||
|
This lets you deploy any number of dynamic language source files as part of an
|
|||
|
application, configure the Spring container to create beans backed by dynamic
|
|||
|
language source files (using the mechanisms described in this chapter), and (later,
|
|||
|
as requirements change or some other external factor comes into play) edit a dynamic
|
|||
|
language source file and have any change they make be reflected in the bean that is
|
|||
|
backed by the changed dynamic language source file. There is no need to shut down a
|
|||
|
running application (or redeploy in the case of a web application). The
|
|||
|
dynamic-language-backed bean so amended picks up the new state and logic from the
|
|||
|
changed dynamic language source file.
|
|||
|
|
|||
|
NOTE: This feature is off by default.
|
|||
|
|
|||
|
Now we can take a look at an example to see how easy it is to start using refreshable
|
|||
|
beans. To turn on the refreshable beans feature, you have to specify exactly one
|
|||
|
additional attribute on the `<lang:language/>` element of your bean definition. So,
|
|||
|
if we stick with <<dynamic-language-a-first-example, the example>> from earlier in
|
|||
|
this chapter, the following example shows what we would change in the Spring XML
|
|||
|
configuration to effect refreshable beans:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<beans>
|
|||
|
|
|||
|
<!-- this bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
|
|||
|
<lang:groovy id="messenger"
|
|||
|
refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
|
|||
|
script-source="classpath:Messenger.groovy">
|
|||
|
<lang:property name="message" value="I Can Do The Frug" />
|
|||
|
</lang:groovy>
|
|||
|
|
|||
|
<bean id="bookingService" class="x.y.DefaultBookingService">
|
|||
|
<property name="messenger" ref="messenger" />
|
|||
|
</bean>
|
|||
|
|
|||
|
</beans>
|
|||
|
----
|
|||
|
|
|||
|
That really is all you have to do. The `refresh-check-delay` attribute defined on the
|
|||
|
`messenger` bean definition is the number of milliseconds after which the bean is
|
|||
|
refreshed with any changes made to the underlying dynamic language source file.
|
|||
|
You can turn off the refresh behavior by assigning a negative value to the
|
|||
|
`refresh-check-delay` attribute. Remember that, by default, the refresh behavior is
|
|||
|
disabled. If you do not want the refresh behavior, do not define the attribute.
|
|||
|
|
|||
|
If we then run the following application, we can exercise the refreshable feature.
|
|||
|
(Please excuse the "`jumping-through-hoops-to-pause-the-execution`" shenanigans
|
|||
|
in this next slice of code.) The `System.in.read()` call is only there so that the
|
|||
|
execution of the program pauses while you (the developer in this scenario) go off
|
|||
|
and edit the underlying dynamic language source file so that the refresh triggers
|
|||
|
on the dynamic-language-backed bean when the program resumes execution.
|
|||
|
|
|||
|
The following listing shows this sample application:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
import org.springframework.context.ApplicationContext;
|
|||
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|||
|
import org.springframework.scripting.Messenger;
|
|||
|
|
|||
|
public final class Boot {
|
|||
|
|
|||
|
public static void main(final String[] args) throws Exception {
|
|||
|
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
|||
|
Messenger messenger = (Messenger) ctx.getBean("messenger");
|
|||
|
System.out.println(messenger.getMessage());
|
|||
|
// pause execution while I go off and make changes to the source file...
|
|||
|
System.in.read();
|
|||
|
System.out.println(messenger.getMessage());
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
Assume then, for the purposes of this example, that all calls to the `getMessage()`
|
|||
|
method of `Messenger` implementations have to be changed such that the message is
|
|||
|
surrounded by quotation marks. The following listing shows the changes that you
|
|||
|
(the developer) should make to the `Messenger.groovy` source file when the
|
|||
|
execution of the program is paused:
|
|||
|
|
|||
|
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting
|
|||
|
|
|||
|
class GroovyMessenger implements Messenger {
|
|||
|
|
|||
|
private String message = "Bingo"
|
|||
|
|
|||
|
public String getMessage() {
|
|||
|
// change the implementation to surround the message in quotes
|
|||
|
return "'" + this.message + "'"
|
|||
|
}
|
|||
|
|
|||
|
public void setMessage(String message) {
|
|||
|
this.message = message
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
When the program runs, the output before the input pause will be `I Can Do The Frug`.
|
|||
|
After the change to the source file is made and saved and the program resumes execution,
|
|||
|
the result of calling the `getMessage()` method on the dynamic-language-backed
|
|||
|
`Messenger` implementation is `'I Can Do The Frug'` (notice the inclusion of the
|
|||
|
additional quotation marks).
|
|||
|
|
|||
|
Changes to a script do not trigger a refresh if the changes occur within the window of
|
|||
|
the `refresh-check-delay` value. Changes to the script are not actually picked up until
|
|||
|
a method is called on the dynamic-language-backed bean. It is only when a method is
|
|||
|
called on a dynamic-language-backed bean that it checks to see if its underlying script
|
|||
|
source has changed. Any exceptions that relate to refreshing the script (such as
|
|||
|
encountering a compilation error or finding that the script file has been deleted)
|
|||
|
results in a fatal exception being propagated to the calling code.
|
|||
|
|
|||
|
The refreshable bean behavior described earlier does not apply to dynamic language
|
|||
|
source files defined with the `<lang:inline-script/>` element notation (see
|
|||
|
<<dynamic-language-beans-inline>>). Additionally, it applies only to beans where
|
|||
|
changes to the underlying source file can actually be detected (for example, by code
|
|||
|
that checks the last modified date of a dynamic language source file that exists on the
|
|||
|
file system).
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-inline]]
|
|||
|
==== Inline Dynamic Language Source Files
|
|||
|
|
|||
|
The dynamic language support can also cater to dynamic language source files that are
|
|||
|
embedded directly in Spring bean definitions. More specifically, the
|
|||
|
`<lang:inline-script/>` element lets you define dynamic language source immediately
|
|||
|
inside a Spring configuration file. An example might clarify how the inline script
|
|||
|
feature works:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<lang:groovy id="messenger">
|
|||
|
<lang:inline-script>
|
|||
|
|
|||
|
package org.springframework.scripting.groovy
|
|||
|
|
|||
|
import org.springframework.scripting.Messenger
|
|||
|
|
|||
|
class GroovyMessenger implements Messenger {
|
|||
|
String message
|
|||
|
}
|
|||
|
|
|||
|
</lang:inline-script>
|
|||
|
<lang:property name="message" value="I Can Do The Frug" />
|
|||
|
</lang:groovy>
|
|||
|
----
|
|||
|
|
|||
|
If we put to one side the issues surrounding whether it is good practice to define
|
|||
|
dynamic language source inside a Spring configuration file, the `<lang:inline-script/>`
|
|||
|
element can be useful in some scenarios. For instance, we might want to quickly add a
|
|||
|
Spring `Validator` implementation to a Spring MVC `Controller`. This is but a moment's
|
|||
|
work using inline source. (See <<dynamic-language-scenarios-validators>> for such an
|
|||
|
example.)
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-ctor-injection]]
|
|||
|
==== Understanding Constructor Injection in the Context of Dynamic-language-backed Beans
|
|||
|
|
|||
|
There is one very important thing to be aware of with regard to Spring's dynamic
|
|||
|
language support. Namely, you can not (currently) supply constructor arguments
|
|||
|
to dynamic-language-backed beans (and, hence, constructor-injection is not available for
|
|||
|
dynamic-language-backed beans). In the interests of making this special handling of
|
|||
|
constructors and properties 100% clear, the following mixture of code and configuration
|
|||
|
does not work:
|
|||
|
|
|||
|
.An approach that cannot work
|
|||
|
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting.groovy
|
|||
|
|
|||
|
import org.springframework.scripting.Messenger
|
|||
|
|
|||
|
// from the file 'Messenger.groovy'
|
|||
|
class GroovyMessenger implements Messenger {
|
|||
|
|
|||
|
GroovyMessenger() {}
|
|||
|
|
|||
|
// this constructor is not available for Constructor Injection
|
|||
|
GroovyMessenger(String message) {
|
|||
|
this.message = message;
|
|||
|
}
|
|||
|
|
|||
|
String message
|
|||
|
|
|||
|
String anotherMessage
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<lang:groovy id="badMessenger"
|
|||
|
script-source="classpath:Messenger.groovy">
|
|||
|
<!-- this next constructor argument will not be injected into the GroovyMessenger -->
|
|||
|
<!-- in fact, this isn't even allowed according to the schema -->
|
|||
|
<constructor-arg value="This will not work" />
|
|||
|
|
|||
|
<!-- only property values are injected into the dynamic-language-backed object -->
|
|||
|
<lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object" />
|
|||
|
|
|||
|
</lang>
|
|||
|
----
|
|||
|
|
|||
|
In practice this limitation is not as significant as it first appears, since setter
|
|||
|
injection is the injection style favored by the overwhelming majority of developers
|
|||
|
(we leave the discussion as to whether that is a good thing to another day).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-groovy]]
|
|||
|
=== Groovy Beans
|
|||
|
|
|||
|
This section describes how to use beans defined in Groovy in Spring.
|
|||
|
|
|||
|
The Groovy homepage includes the following description:
|
|||
|
|
|||
|
"`Groovy is an agile dynamic language for the Java 2 Platform that has many of the
|
|||
|
features that people like so much in languages like Python, Ruby and Smalltalk, making
|
|||
|
them available to Java developers using a Java-like syntax.`"
|
|||
|
|
|||
|
If you have read this chapter straight from the top, you have already
|
|||
|
<<dynamic-language-a-first-example, seen an example>> of a Groovy-dynamic-language-backed
|
|||
|
bean. Now consider another example (again using an example from the Spring test suite):
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting;
|
|||
|
|
|||
|
public interface Calculator {
|
|||
|
|
|||
|
int add(int x, int y);
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The following example implements the `Calculator` interface in Groovy:
|
|||
|
|
|||
|
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting.groovy
|
|||
|
|
|||
|
// from the file 'calculator.groovy'
|
|||
|
class GroovyCalculator implements Calculator {
|
|||
|
|
|||
|
int add(int x, int y) {
|
|||
|
x + y
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The following bean definition uses the calculator defined in Groovy:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<!-- from the file 'beans.xml' -->
|
|||
|
<beans>
|
|||
|
<lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
|
|||
|
</beans>
|
|||
|
----
|
|||
|
|
|||
|
Finally, the following small application exercises the preceding configuration:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting;
|
|||
|
|
|||
|
import org.springframework.context.ApplicationContext;
|
|||
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|||
|
|
|||
|
public class Main {
|
|||
|
|
|||
|
public static void main(String[] args) {
|
|||
|
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
|
|||
|
Calculator calc = ctx.getBean("calculator", Calculator.class);
|
|||
|
System.out.println(calc.add(2, 8));
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The resulting output from running the above program is (unsurprisingly) `10`.
|
|||
|
(For more interesting examples, see the dynamic language showcase project for a more
|
|||
|
complex example or see the examples <<dynamic-language-scenarios>> later in this chapter).
|
|||
|
|
|||
|
You must not define more than one class per Groovy source file. While this is perfectly
|
|||
|
legal in Groovy, it is (arguably) a bad practice. In the interests of a consistent
|
|||
|
approach, you should (in the opinion of the Spring team) respect the standard Java
|
|||
|
conventions of one (public) class per source file.
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-groovy-customizer]]
|
|||
|
==== Customizing Groovy Objects by Using a Callback
|
|||
|
|
|||
|
The `GroovyObjectCustomizer` interface is a callback that lets you hook additional
|
|||
|
creation logic into the process of creating a Groovy-backed bean. For example,
|
|||
|
implementations of this interface could invoke any required initialization methods,
|
|||
|
set some default property values, or specify a custom `MetaClass`. The following listing
|
|||
|
shows the `GroovyObjectCustomizer` interface definition:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
public interface GroovyObjectCustomizer {
|
|||
|
|
|||
|
void customize(GroovyObject goo);
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The Spring Framework instantiates an instance of your Groovy-backed bean and then
|
|||
|
passes the created `GroovyObject` to the specified `GroovyObjectCustomizer` (if one
|
|||
|
has been defined). You can do whatever you like with the supplied `GroovyObject`
|
|||
|
reference. We expect that most people want to set a custom `MetaClass` with this
|
|||
|
callback, and the following example shows how to do so:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer {
|
|||
|
|
|||
|
public void customize(GroovyObject goo) {
|
|||
|
DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) {
|
|||
|
|
|||
|
public Object invokeMethod(Object object, String methodName, Object[] arguments) {
|
|||
|
System.out.println("Invoking '" + methodName + "'.");
|
|||
|
return super.invokeMethod(object, methodName, arguments);
|
|||
|
}
|
|||
|
};
|
|||
|
metaClass.initialize();
|
|||
|
goo.setMetaClass(metaClass);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
A full discussion of meta-programming in Groovy is beyond the scope of the Spring
|
|||
|
reference manual. See the relevant section of the Groovy reference manual or do a
|
|||
|
search online. Plenty of articles address this topic. Actually, making use of a
|
|||
|
`GroovyObjectCustomizer` is easy if you use the Spring namespace support, as the
|
|||
|
following example shows:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<!-- define the GroovyObjectCustomizer just like any other bean -->
|
|||
|
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
|
|||
|
|
|||
|
<!-- ... and plug it into the desired Groovy bean via the 'customizer-ref' attribute -->
|
|||
|
<lang:groovy id="calculator"
|
|||
|
script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy"
|
|||
|
customizer-ref="tracingCustomizer"/>
|
|||
|
----
|
|||
|
|
|||
|
If you do not use the Spring namespace support, you can still use the
|
|||
|
`GroovyObjectCustomizer` functionality, as the following example shows:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
|
|||
|
<constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
|
|||
|
<!-- define the GroovyObjectCustomizer (as an inner bean) -->
|
|||
|
<constructor-arg>
|
|||
|
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer"/>
|
|||
|
</constructor-arg>
|
|||
|
</bean>
|
|||
|
|
|||
|
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
|
|||
|
----
|
|||
|
|
|||
|
NOTE: You may also specify a Groovy `CompilationCustomizer` (such as an `ImportCustomizer`)
|
|||
|
or even a full Groovy `CompilerConfiguration` object in the same place as Spring's
|
|||
|
`GroovyObjectCustomizer`. Furthermore, you may set a common `GroovyClassLoader` with custom
|
|||
|
configuration for your beans at the `ConfigurableApplicationContext.setClassLoader` level;
|
|||
|
this also leads to shared `GroovyClassLoader` usage and is therefore recommendable in case of
|
|||
|
a large number of scripted beans (avoiding an isolated `GroovyClassLoader` instance per bean).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-beans-bsh]]
|
|||
|
=== BeanShell Beans
|
|||
|
|
|||
|
This section describes how to use BeanShell beans in Spring.
|
|||
|
|
|||
|
The https://beanshell.github.io/intro.html[BeanShell homepage] includes the following
|
|||
|
description:
|
|||
|
|
|||
|
----
|
|||
|
BeanShell is a small, free, embeddable Java source interpreter with dynamic language
|
|||
|
features, written in Java. BeanShell dynamically runs standard Java syntax and
|
|||
|
extends it with common scripting conveniences such as loose types, commands, and method
|
|||
|
closures like those in Perl and JavaScript.
|
|||
|
----
|
|||
|
|
|||
|
In contrast to Groovy, BeanShell-backed bean definitions require some (small) additional
|
|||
|
configuration. The implementation of the BeanShell dynamic language support in Spring is
|
|||
|
interesting, because Spring creates a JDK dynamic proxy that implements all of the
|
|||
|
interfaces that are specified in the `script-interfaces` attribute value of the
|
|||
|
`<lang:bsh>` element (this is why you must supply at least one interface in the value
|
|||
|
of the attribute, and, consequently, program to interfaces when you use BeanShell-backed
|
|||
|
beans). This means that every method call on a BeanShell-backed object goes through the
|
|||
|
JDK dynamic proxy invocation mechanism.
|
|||
|
|
|||
|
Now we can show a fully working example of using a BeanShell-based bean that implements
|
|||
|
the `Messenger` interface that was defined earlier in this chapter. We again show the
|
|||
|
definition of the `Messenger` interface:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.scripting;
|
|||
|
|
|||
|
public interface Messenger {
|
|||
|
|
|||
|
String getMessage();
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The following example shows the BeanShell "`implementation`" (we use the term loosely here)
|
|||
|
of the `Messenger` interface:
|
|||
|
|
|||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
String message;
|
|||
|
|
|||
|
String getMessage() {
|
|||
|
return message;
|
|||
|
}
|
|||
|
|
|||
|
void setMessage(String aMessage) {
|
|||
|
message = aMessage;
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
The following example shows the Spring XML that defines an "`instance`" of the above
|
|||
|
"`class`" (again, we use these terms very loosely here):
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
|
|||
|
script-interfaces="org.springframework.scripting.Messenger">
|
|||
|
|
|||
|
<lang:property name="message" value="Hello World!" />
|
|||
|
</lang:bsh>
|
|||
|
----
|
|||
|
|
|||
|
See <<dynamic-language-scenarios>> for some scenarios where you might want to use
|
|||
|
BeanShell-based beans.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-scenarios]]
|
|||
|
== Scenarios
|
|||
|
|
|||
|
The possible scenarios where defining Spring managed beans in a scripting language would
|
|||
|
be beneficial are many and varied. This section describes two possible use cases for the
|
|||
|
dynamic language support in Spring.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-scenarios-controllers]]
|
|||
|
=== Scripted Spring MVC Controllers
|
|||
|
|
|||
|
One group of classes that can benefit from using dynamic-language-backed beans is that
|
|||
|
of Spring MVC controllers. In pure Spring MVC applications, the navigational flow
|
|||
|
through a web application is, to a large extent, determined by code encapsulated within
|
|||
|
your Spring MVC controllers. As the navigational flow and other presentation layer logic
|
|||
|
of a web application needs to be updated to respond to support issues or changing
|
|||
|
business requirements, it may well be easier to effect any such required changes by
|
|||
|
editing one or more dynamic language source files and seeing those changes being
|
|||
|
immediately reflected in the state of a running application.
|
|||
|
|
|||
|
Remember that, in the lightweight architectural model espoused by projects such as
|
|||
|
Spring, you typically aim to have a really thin presentation layer, with all
|
|||
|
the meaty business logic of an application being contained in the domain and service
|
|||
|
layer classes. Developing Spring MVC controllers as dynamic-language-backed beans lets
|
|||
|
you change presentation layer logic by editing and saving text files. Any
|
|||
|
changes to such dynamic language source files is (depending on the configuration)
|
|||
|
automatically reflected in the beans that are backed by dynamic language source files.
|
|||
|
|
|||
|
NOTE: To effect this automatic "`pickup`" of any changes to dynamic-language-backed
|
|||
|
beans, you have to enable the "`refreshable beans`" functionality. See
|
|||
|
<<dynamic-language-refreshable-beans>> for a full treatment of this feature.
|
|||
|
|
|||
|
The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
|
|||
|
by using the Groovy dynamic language:
|
|||
|
|
|||
|
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
|||
|
----
|
|||
|
package org.springframework.showcase.fortune.web
|
|||
|
|
|||
|
import org.springframework.showcase.fortune.service.FortuneService
|
|||
|
import org.springframework.showcase.fortune.domain.Fortune
|
|||
|
import org.springframework.web.servlet.ModelAndView
|
|||
|
import org.springframework.web.servlet.mvc.Controller
|
|||
|
|
|||
|
import jakarta.servlet.http.HttpServletRequest
|
|||
|
import jakarta.servlet.http.HttpServletResponse
|
|||
|
|
|||
|
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
|||
|
class FortuneController implements Controller {
|
|||
|
|
|||
|
@Property FortuneService fortuneService
|
|||
|
|
|||
|
ModelAndView handleRequest(HttpServletRequest request,
|
|||
|
HttpServletResponse httpServletResponse) {
|
|||
|
return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<lang:groovy id="fortune"
|
|||
|
refresh-check-delay="3000"
|
|||
|
script-source="/WEB-INF/groovy/FortuneController.groovy">
|
|||
|
<lang:property name="fortuneService" ref="fortuneService"/>
|
|||
|
</lang:groovy>
|
|||
|
----
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-scenarios-validators]]
|
|||
|
=== Scripted Validators
|
|||
|
|
|||
|
Another area of application development with Spring that may benefit from the
|
|||
|
flexibility afforded by dynamic-language-backed beans is that of validation. It can
|
|||
|
be easier to express complex validation logic by using a loosely typed dynamic language
|
|||
|
(that may also have support for inline regular expressions) as opposed to regular Java.
|
|||
|
|
|||
|
Again, developing validators as dynamic-language-backed beans lets you change
|
|||
|
validation logic by editing and saving a simple text file. Any such changes is
|
|||
|
(depending on the configuration) automatically reflected in the execution of a
|
|||
|
running application and would not require the restart of an application.
|
|||
|
|
|||
|
NOTE: To effect the automatic "`pickup`" of any changes to dynamic-language-backed
|
|||
|
beans, you have to enable the 'refreshable beans' feature. See
|
|||
|
<<dynamic-language-refreshable-beans>> for a full and detailed treatment of this feature.
|
|||
|
|
|||
|
The following example shows a Spring `org.springframework.validation.Validator`
|
|||
|
implemented by using the Groovy dynamic language (see <<core.adoc#validator,
|
|||
|
Validation using Spring’s Validator interface>> for a discussion of the
|
|||
|
`Validator` interface):
|
|||
|
|
|||
|
[source,groovy,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
import org.springframework.validation.Validator
|
|||
|
import org.springframework.validation.Errors
|
|||
|
import org.springframework.beans.TestBean
|
|||
|
|
|||
|
class TestBeanValidator implements Validator {
|
|||
|
|
|||
|
boolean supports(Class clazz) {
|
|||
|
return TestBean.class.isAssignableFrom(clazz)
|
|||
|
}
|
|||
|
|
|||
|
void validate(Object bean, Errors errors) {
|
|||
|
if(bean.name?.trim()?.size() > 0) {
|
|||
|
return
|
|||
|
}
|
|||
|
errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
|
|||
|
}
|
|||
|
}
|
|||
|
----
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-final-notes]]
|
|||
|
== Additional Details
|
|||
|
|
|||
|
This last section contains some additional details related to the dynamic language support.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-final-notes-aop]]
|
|||
|
=== AOP -- Advising Scripted Beans
|
|||
|
|
|||
|
You can use the Spring AOP framework to advise scripted beans. The Spring AOP
|
|||
|
framework actually is unaware that a bean that is being advised might be a scripted
|
|||
|
bean, so all of the AOP use cases and functionality that you use (or aim to use)
|
|||
|
work with scripted beans. When you advise scripted beans, you cannot use class-based
|
|||
|
proxies. You must use <<core.adoc#aop-proxying, interface-based proxies>>.
|
|||
|
|
|||
|
You are not limited to advising scripted beans. You can also write aspects themselves
|
|||
|
in a supported dynamic language and use such beans to advise other Spring beans.
|
|||
|
This really would be an advanced use of the dynamic language support though.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-final-notes-scopes]]
|
|||
|
=== Scoping
|
|||
|
|
|||
|
In case it is not immediately obvious, scripted beans can be scoped in the same way as
|
|||
|
any other bean. The `scope` attribute on the various `<lang:language/>` elements lets
|
|||
|
you control the scope of the underlying scripted bean, as it does with a regular
|
|||
|
bean. (The default scope is <<core.adoc#beans-factory-scopes-singleton, singleton>>,
|
|||
|
as it is with "`regular`" beans.)
|
|||
|
|
|||
|
The following example uses the `scope` attribute to define a Groovy bean scoped as
|
|||
|
a <<core.adoc#beans-factory-scopes-prototype, prototype>>:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<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 https://www.springframework.org/schema/beans/spring-beans.xsd
|
|||
|
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
|||
|
|
|||
|
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy" scope="prototype">
|
|||
|
<lang:property name="message" value="I Can Do The RoboCop" />
|
|||
|
</lang:groovy>
|
|||
|
|
|||
|
<bean id="bookingService" class="x.y.DefaultBookingService">
|
|||
|
<property name="messenger" ref="messenger" />
|
|||
|
</bean>
|
|||
|
|
|||
|
</beans>
|
|||
|
----
|
|||
|
|
|||
|
See <<core.adoc#beans-factory-scopes, Bean Scopes>> in <<core.adoc#beans, The IoC Container>>
|
|||
|
for a full discussion of the scoping support in the Spring Framework.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[xsd-schemas-lang]]
|
|||
|
=== The `lang` XML schema
|
|||
|
|
|||
|
The `lang` elements in Spring XML configuration deal with exposing objects that have been
|
|||
|
written in a dynamic language (such as Groovy or BeanShell) as beans in the Spring container.
|
|||
|
|
|||
|
These elements (and the dynamic language support) are comprehensively covered in
|
|||
|
<<dynamic-language, Dynamic Language Support>>. See that section
|
|||
|
for full details on this support and the `lang` elements.
|
|||
|
|
|||
|
To use the elements in the `lang` schema, you need to have the following preamble at the
|
|||
|
top of your Spring XML configuration file. The text in the following snippet references
|
|||
|
the correct schema so that the tags in the `lang` namespace are available to you:
|
|||
|
|
|||
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|||
|
----
|
|||
|
<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<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 https://www.springframework.org/schema/beans/spring-beans.xsd
|
|||
|
http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd">
|
|||
|
|
|||
|
<!-- bean definitions here -->
|
|||
|
|
|||
|
</beans>
|
|||
|
----
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
[[dynamic-language-resources]]
|
|||
|
== Further Resources
|
|||
|
|
|||
|
The following links go to further resources about the various dynamic languages referenced
|
|||
|
in this chapter:
|
|||
|
|
|||
|
* The https://www.groovy-lang.org/[Groovy] homepage
|
|||
|
* The https://beanshell.github.io/intro.html[BeanShell] homepage
|
|||
|
* The https://www.jruby.org[JRuby] homepage
|