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
import org.junit.jupiter.api.Test
import static groovy.test.GroovyAssert.*
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 Scripts 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. JavaShell
While GroovyShell and GroovyClassLoader compile Groovy source, org.apache.groovy.util.JavaShell
compiles Java source in-memory using the platform javax.tools.JavaCompiler. It is useful when a
Groovy or polyglot application needs to compile and load Java classes at runtime: code generation,
plug-in loaders, scripting-style Java endpoints, or mixed-language testing setups.
JavaShell requires a JDK at runtime, not just a JRE — ToolProvider.getSystemJavaCompiler()
returns null on a JRE.
|
The simplest usage compiles a single source string and returns the loaded Class:
import org.apache.groovy.util.JavaShell
def js = new JavaShell() (1)
def src = """
package demo;
public class Foo {
public static String greet(String who) { return "hello, " + who; }
}
"""
Class<?> foo = js.compile('demo.Foo', src) (2)
assert foo.getDeclaredMethod('greet', String).invoke(null, 'world') == 'hello, world' (3)
| 1 | create a new JavaShell (an optional ClassLoader argument sets the parent loader) |
| 2 | compile the source; className is the fully-qualified binary name and must match the
package declaration — see the JavaShell javadoc for the full naming rules |
| 3 | invoke the compiled method reflectively |
compileAll returns every class produced from the source unit — useful when the source declares
auxiliary or nested classes:
import org.apache.groovy.util.JavaShell
def js = new JavaShell()
def src = """
package demo;
public class Box { public static int hidden() { return Helper.value(); } }
class Helper { static int value() { return 42; } }
"""
Map<String, Class<?>> classes = js.compileAll('demo.Box', src) (1)
assert classes.keySet() == ['demo.Box', 'demo.Helper'] as Set (2)
assert classes['demo.Box'].getDeclaredMethod('hidden').invoke(null) == 42
| 1 | compile a primary public class together with its package-private helper |
| 2 | the returned map is keyed by binary class name |
To additionally persist the generated class files to disk (for tools that consume .class files
or to seed a classpath outside the JVM), use compileAllTo. It accepts a Path output directory
and writes each class to its conventional package subdirectory:
import org.apache.groovy.util.JavaShell
import java.nio.file.Files
def out = Files.createTempDirectory('javashell-')
try {
def js = new JavaShell()
def src = """
package demo;
public class Deep { public static String tag(String label) { return label; } }
"""
def written = js.compileAllTo('demo.Deep', ['-parameters'], src, out) (1)
def classFile = out.resolve('demo/Deep.class') (2)
assert written['demo.Deep'] == classFile && Files.exists(classFile)
// classes also remain available through js.classLoader, same as compileAll
assert js.classLoader.loadClass('demo.Deep').getDeclaredMethod('tag', String).invoke(null, 'x') == 'x'
} finally {
out.toFile().deleteDir()
}
| 1 | compile and write to disk in one call; the second argument passes javac options (here
-parameters to retain formal parameter names) |
| 2 | packaged classes land under <outputDir>/<package as path>/<ClassName>.class; default-package
classes land directly under outputDir |
The compiled classes remain available through js.getClassLoader() after compileAllTo returns,
so the same instance covers both "compile to memory" and "compile to memory and disk".
When the source fails to compile, JavaShell throws JavaShellCompilationException carrying the
javac diagnostics. See the JavaShell javadoc for the full set of overloads (with/without an
Iterable<String> options argument) and the expected className/options formats.
compileAllTo and its no-options overload are marked @Incubating — their signatures may
evolve in a subsequent 6.x release based on usage feedback.
|
1.6. 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.