Integrating Groovy into applications
1. Groovy integration mechanisms
The Groovy language proposes several ways to integrate itself into applications (Java or even Groovy) at runtime, from the most basic, simple code execution to the most complete, integrating caching and compiler customization.
All the examples written in this section are using Groovy, but the same integration mechanisms can be used from Java. |
1.1. Eval
The groovy.util.Eval
class is the simplest way to execute Groovy dynamically at runtime. This can be done by calling the me
method:
import groovy.util.Eval
assert Eval.me('33*3') == 99
assert Eval.me('"foo".toUpperCase()') == 'FOO'
Eval
supports multiple variants that accept parameters for simple evaluation:
assert Eval.x(4, '2*x') == 8 (1)
assert Eval.me('k', 4, '2*k') == 8 (2)
assert Eval.xy(4, 5, 'x*y') == 20 (3)
assert Eval.xyz(4, 5, 6, 'x*y+z') == 26 (4)
1 | Simple evaluation with one bound parameter named x |
2 | Same evaluation, with a custom bound parameter named k |
3 | Simple evaluation with two bound parameters named x and y |
4 | Simple evaluation with three bound parameters named x , y and z |
The Eval
class makes it very easy to evaluate simple scripts, but doesn’t scale: there is no caching of the script, and
it isn’t meant to evaluate more than one-liners.
1.2. GroovyShell
1.2.1. Multiple sources
The groovy.lang.GroovyShell
class is the preferred way to evaluate scripts with the ability to cache the resulting
script instance. Although the Eval
class returns the result of the execution of the compiled script, the GroovyShell
class offers more options.
def shell = new GroovyShell() (1)
def result = shell.evaluate '3*5' (2)
def result2 = shell.evaluate(new StringReader('3*5')) (3)
assert result == result2
def script = shell.parse '3*5' (4)
assert script instanceof groovy.lang.Script
assert script.run() == 15 (5)
1 | create a new GroovyShell instance |
2 | can be used as Eval with direct execution of the code |
3 | can read from multiple sources (String , Reader , File , InputStream ) |
4 | can defer execution of the script. parse returns a Script instance |
5 | Script defines a run method |
1.2.2. Sharing data between a script and the application
It is possible to share data between the application and the script using a groovy.lang.Binding
:
def sharedData = new Binding() (1)
def shell = new GroovyShell(sharedData) (2)
def now = new Date()
sharedData.setProperty('text', 'I am shared data!') (3)
sharedData.setProperty('date', now) (4)
String result = shell.evaluate('"At $date, $text"') (5)
assert result == "At $now, I am shared data!"
1 | create a new Binding that will contain shared data |
2 | create a GroovyShell using this shared data |
3 | add a string to the binding |
4 | add a date to the binding (you are not limited to simple types) |
5 | evaluate the script |
Note that it is also possible to write from the script into the binding:
def sharedData = new Binding() (1)
def shell = new GroovyShell(sharedData) (2)
shell.evaluate('foo=123') (3)
assert sharedData.getProperty('foo') == 123 (4)
1 | create a new Binding instance |
2 | create a new GroovyShell using that shared data |
3 | use an undeclared variable to store the result into the binding |
4 | read the result from the caller |
It is important to understand that you need to use an undeclared variable if you want to write into the binding. Using
def
or an explicit
type like in the example below would fail because you would then create a local variable:
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate('int foo=123')
try {
assert sharedData.getProperty('foo')
} catch (MissingPropertyException e) {
println "foo is defined as a local variable"
}
You must be very careful when using shared data in a multithreaded environment. The Binding instance that
you pass to GroovyShell is not thread safe, and shared by all scripts.
|
It is possible to work around the shared instance of Binding
by leveraging the Script
instance which is returned
by parse
:
def shell = new GroovyShell()
def b1 = new Binding(x:3) (1)
def b2 = new Binding(x:4) (2)
def script = shell.parse('x = 2*x')
script.binding = b1
script.run()
script.binding = b2
script.run()
assert b1.getProperty('x') == 6
assert b2.getProperty('x') == 8
assert b1 != b2
1 | will store the x variable inside b1 |
2 | will store the x variable inside b2 |
However, you must be aware that you are still sharing the same instance of a script. So this technique cannot be used if you have two threads working on the same script. In that case, you must make sure of creating two distinct script instances:
def shell = new GroovyShell()
def b1 = new Binding(x:3)
def b2 = new Binding(x:4)
def script1 = shell.parse('x = 2*x') (1)
def script2 = shell.parse('x = 2*x') (2)
assert script1 != script2
script1.binding = b1 (3)
script2.binding = b2 (4)
def t1 = Thread.start { script1.run() } (5)
def t2 = Thread.start { script2.run() } (6)
[t1,t2]*.join() (7)
assert b1.getProperty('x') == 6
assert b2.getProperty('x') == 8
assert b1 != b2
1 | create an instance of script for thread 1 |
2 | create an instance of script for thread 2 |
3 | assign first binding to script 1 |
4 | assign second binding to script 2 |
5 | start first script in a separate thread |
6 | start second script in a separate thread |
7 | wait for completion |
In case you need thread safety like here, it is more advisable to use the GroovyClassLoader directly instead.
1.2.3. Custom script class
We have seen that the parse
method returns an instance of groovy.lang.Script
, but it is possible to use a custom
class, given that it extends Script
itself. It can be used to provide additional behavior to the script like in
the example below:
abstract class MyScript extends Script {
String name
String greet() {
"Hello, $name!"
}
}
The custom class defines a property called name
and a new method called greet
. This class can be used as the script
base class by using a custom configuration:
import org.codehaus.groovy.control.CompilerConfiguration
def config = new CompilerConfiguration() (1)
config.scriptBaseClass = 'MyScript' (2)
def shell = new GroovyShell(this.class.classLoader, new Binding(), config) (3)
def script = shell.parse('greet()') (4)
assert script instanceof MyScript
script.setName('Michel')
assert script.run() == 'Hello, Michel!'
1 | create a CompilerConfiguration instance |
2 | instruct it to use MyScript as the base class for scripts |
3 | then use the compiler configuration when you create the shell |
4 | the script now has access to the new method greet |
You are not limited to the sole scriptBaseClass configuration. You can use any of the compiler configuration tweaks, including the compilation customizers. |
1.3. GroovyClassLoader
In the previous section, we have shown that GroovyShell
was an easy tool to execute scripts, but
it makes it complicated to compile anything but scripts. Internally, it makes use of the groovy.lang.GroovyClassLoader
,
which is at the heart of the compilation and loading of classes at runtime.
By leveraging the GroovyClassLoader
instead of GroovyShell
, you will be able to load classes, instead of instances
of scripts:
import groovy.lang.GroovyClassLoader
def gcl = new GroovyClassLoader() (1)
def clazz = gcl.parseClass('class Foo { void doIt() { println "ok" } }') (2)
assert clazz.name == 'Foo' (3)
def o = clazz.newInstance() (4)
o.doIt() (5)
1 | create a new GroovyClassLoader |
2 | parseClass will return an instance of Class |
3 | you can check that the class which is returns is really the one defined in the script |
4 | and you can create a new instance of the class, which is not a script |
5 | then call any method on it |
A GroovyClassLoader keeps a reference of all the classes it created, so it is easy to create a memory leak. In particular, if you execute the same script twice, if it is a String, then you obtain two distinct classes! |
import groovy.lang.GroovyClassLoader
def gcl = new GroovyClassLoader()
def clazz1 = gcl.parseClass('class Foo { }') (1)
def clazz2 = gcl.parseClass('class Foo { }') (2)
assert clazz1.name == 'Foo' (3)
assert clazz2.name == 'Foo'
assert clazz1 != clazz2 (4)
1 | dynamically create a class named "Foo" |
2 | create an identical looking class, using a separate parseClass call |
3 | make sure both classes have the same name |
4 | but they are actually different! |
The reason is that a GroovyClassLoader
doesn’t keep track of the source text. If you want to have the same instance,
then the source must be a file, like in this example:
def gcl = new GroovyClassLoader()
def clazz1 = gcl.parseClass(file) (1)
def clazz2 = gcl.parseClass(new File(file.absolutePath)) (2)
assert clazz1.name == 'Foo' (3)
assert clazz2.name == 'Foo'
assert clazz1 == clazz2 (4)
1 | parse a class from a File |
2 | parse a class from a distinct file instance, but pointing to the same physical file |
3 | make sure our classes have the same name |
4 | but now, they are the same instance |
Using a File
as input, the GroovyClassLoader
is capable of caching the generated class file, which avoids
creating multiple classes at runtime for the same source.
1.4. GroovyScriptEngine
The groovy.util.GroovyScriptEngine
class provides a flexible foundation for applications which rely on script
reloading and script dependencies. While GroovyShell
focuses on standalone Script`s and `GroovyClassLoader
handles
dynamic compilation and loading of any Groovy class, the GroovyScriptEngine
will add a layer on top of GroovyClassLoader
to handle both script dependencies and reloading.
To illustrate this, we will create a script engine and execute code in an infinite loop. First of all, you need to create a directory with the following script inside:
class Greeter {
String sayHello() {
def greet = "Hello, world!"
greet
}
}
new Greeter()
then you can execute this code using a GroovyScriptEngine
:
def binding = new Binding()
def engine = new GroovyScriptEngine([tmpDir.toURI().toURL()] as URL[]) (1)
while (true) {
def greeter = engine.run('ReloadingTest.groovy', binding) (2)
println greeter.sayHello() (3)
Thread.sleep(1000)
}
1 | create a script engine which will look for sources into our source directory |
2 | execute the script, which will return an instance of Greeter |
3 | print the greeting message |
At this point, you should see a message printed every second:
Hello, world! Hello, world! ...
Without interrupting the script execution, now replace the contents of the ReloadingTest
file with:
class Greeter {
String sayHello() {
def greet = "Hello, Groovy!"
greet
}
}
new Greeter()
And the message should change to:
Hello, world! ... Hello, Groovy! Hello, Groovy! ...
But it is also possible to have a dependency on another script. To illustrate this, create the following file into the same directory, without interrupting the executing script:
class Dependency {
String message = 'Hello, dependency 1'
}
and update the ReloadingTest
script like this:
import Dependency
class Greeter {
String sayHello() {
def greet = new Dependency().message
greet
}
}
new Greeter()
And this time, the message should change to:
Hello, Groovy! ... Hello, dependency 1! Hello, dependency 1! ...
And as a last test, you can update the Dependency.groovy
file without touching the ReloadingTest
file:
class Dependency {
String message = 'Hello, dependency 2'
}
And you should observe that the dependent file was reloaded:
Hello, dependency 1! ... Hello, dependency 2! Hello, dependency 2!
1.5. CompilationUnit
Ultimately, it is possible to perform more operations during compilation by relying directly on the
org.codehaus.groovy.control.CompilationUnit
class. This class is responsible for determining the various steps of
compilation and would let you introduce new steps or even stop compilation at various phases. This is for example how
stub generation is done, for the joint compiler.
However, overriding CompilationUnit
is not recommended and should only be done if no other standard solution works.
2. JSR 223 javax.script API
JSR-223 is a standard API for calling scripting frameworks in Java. It is available since Java 6 and aims at providing a common framework for calling multiple languages from Java. Groovy provides its own richer integration mechanisms, and if you don’t plan to use multiple languages in the same application, it is recommended that you use the Groovy integration mechanisms instead of the limited JSR-223 API. |
Here is how you need to initialize the JSR-223 engine to talk to Groovy from Java:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
...
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("groovy");
Then you can execute Groovy scripts easily:
Integer sum = (Integer) engine.eval("(1..10).sum()");
assertEquals(Integer.valueOf(55), sum);
It is also possible to share variables:
engine.put("first", "HELLO");
engine.put("second", "world");
String result = (String) engine.eval("first.toLowerCase() + ' ' + second.toUpperCase()");
assertEquals("hello WORLD", result);
This next example illustrates calling an invokable function:
import javax.script.Invocable;
...
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("groovy");
String fact = "def factorial(n) { n == 1 ? 1 : n * factorial(n - 1) }";
engine.eval(fact);
Invocable inv = (Invocable) engine;
Object[] params = {5};
Object result = inv.invokeFunction("factorial", params);
assertEquals(Integer.valueOf(120), result);
The engine keeps per default hard references to the script functions. To
change this you should set an engine level scoped attribute to the script
context of the name #jsr223.groovy.engine.keep.globals
with a
String being phantom
to use phantom references, weak
to use weak
references or soft
to use soft references - casing is ignored. Any
other string will cause the use of hard references.