blob: 094bd18a43c36ae907498a61b99c6c03a5958f26 [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Integrating Groovy in a Java application
== 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.
TIP: All the examples written in this section are using Groovy, but the same integration mechanisms can be used from
Java.
[[integ-eval]]
=== 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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=eval_me,indent=0]
----
`Eval` supports multiple variants that accept parameters for simple evaluation:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=eval_x,indent=0]
----
<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.
[[integ-groovyshell]]
=== GroovyShell
==== 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.
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=groovyshell_simple,indent=0]
----
<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
==== 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`:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=groovyshell_binding,indent=0]
----
<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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=groovyshell_binding_output,indent=0]
----
<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_:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=groovyshell_binding_localvariable,indent=0]
----
WARNING: 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`:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=groovyshell_parse_binding,indent=0]
----
<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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=groovyshell_threadsafe,indent=0]
----
<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, GroovyClassLoader>>
directly instead.
==== 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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=custom_script_scriptclass,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=custom_script_imports,indent=0]
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=custom_script_usage,indent=0]
----
<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`
NOTE: You are not limited to the sole _scriptBaseClass_ configuration. You can use any of the compiler configuration
tweaks, including the <<compilation-customizers,compilation customizers>>.
[[groovyclassloader]]
=== GroovyClassLoader
In the <<integ-groovyshell,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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gcl,indent=0]
----
<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
NOTE: 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!
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gcl_distinct_classes,indent=0]
----
<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:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gcl_same_classes,indent=0]
----
<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.
=== 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:
[source,groovy]
.ReloadingTest.groovy
----
include::{projectdir}/src/spec/test-resources/reloading/source1.groovy[tags=greeter,indent=0]
----
then you can execute this code using a `GroovyScriptEngine`:
[source,groovy]
----
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gse_init,indent=0]
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gse_script_fakeloop_begin,indent=0]
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gse_script1,indent=4]
include::{projectdir}/src/spec/test/IntegrationTest.groovy[tags=gse_script_fakeloop_end,indent=0]
----
<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:
[source,groovy]
.ReloadingTest.groovy
----
include::{projectdir}/src/spec/test-resources/reloading/source2.groovy[tags=greeter,indent=0]
----
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:
[source,groovy]
.Depencency.groovy
----
include::{projectdir}/src/spec/test-resources/reloading/dependency1.groovy[tags=dependency,indent=0]
----
and update the `ReloadingTest` script like this:
[source,groovy]
.ReloadingTest.groovy
----
include::{projectdir}/src/spec/test-resources/reloading/source3.groovy[tags=greeter,indent=0]
----
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:
[source,groovy]
.Depencency.groovy
----
include::{projectdir}/src/spec/test-resources/reloading/dependency2.groovy[tags=dependency,indent=0]
----
And you should observe that the dependent file was reloaded:
----
Hello, dependency 1!
...
Hello, dependency 2!
Hello, dependency 2!
----
=== 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.
include::{projectdir}/subprojects/groovy-bsf/{specfolder}/integrating-bsf.adoc[leveloffset=+1]
include::{projectdir}/subprojects/groovy-jsr223/{specfolder}/integrating-jsr223.adoc[leveloffset=+1]