Groovy Scripts - How do they work?
If you are already familiar with Groovy, you know that Groovy supports script mode where you can write Groovy code without explicitly creating a class. This mode is suitable when you want to house the entire code in a single class. If you are new to Groovy, you might want to get a taste of Groovy from this post.
The focus of this post is to explore how to scripts work in Groovy. Let's take a look at the following code snippets to help us appreciate the script mode.
Greeter.groovy:
class Greeter {
static void main(String[] args) {
String message = args.length > 0 ? "Hello ${args[0]}" : "Hello"
println message
}
}
GreeterScript.groovy
String message = args.length > 0 ? "Hello ${args[0]}" : "Hello"
println message
Let's quickly run both the code examples. Note that I have the files in a directory named 'demo'.
➜ demo groovy Greeter.groovy
Hello
➜ demo groovy Greeter.groovy Groovy
Hello Groovy
➜ demo groovy GreeterScript.groovy
Hello
➜ demo groovy GreeterScript.groovy Groovy
Hello Groovy
Looking at the results, we are sure that they behave the same. The script version of the code has fewer elements and hence simpler. If you look at Greeter.groovy
, the class Greeter
is here just to comply with the compiler. Since there is only one class, it doesn't contribute to the design aspects such as modularity or readability. Hence it is a noise from the developers perspective. While the presence of a class is necessary for JVM to execute the code, it doesn't necessarily have to be created by the developer. Tools can take away that responsibility from the developer, which is exactly what Groovy is doing here.
Does it mean that Groovy compiler created a class with a main
method and placed our script code inside it? Let's find out with the help of 'groovyConsole' tool. Let's first open 'GreeterScript.groovy' from 'groovyConsole' with the following command.
➜ demo groovyConsole GreeterScript.groovy
Once you have the file open in 'groovyConsole' use "CMD+T" keyboard shortcut or "Script -> Inspect AST" from the menu. We see the following code at the end of 'Semantic Analysis' phase (there is a drop-down at the top of the Groovy AST Browser window).
public class com.nareshak.demo.script1596181510580 extends groovy.lang.Script {
public com.nareshak.demo.script1596181510580() {
}
public com.nareshak.demo.script1596181510580(groovy.lang.Binding context) {
super(context)
}
public static void main(java.lang.String[] args) {
org.codehaus.groovy.runtime.InvokerHelper.runScript(com.nareshak.demo.script1596181510580, args)
}
public java.lang.Object run() {
java.lang.String message = args .length > 0 ? "Hello $args[0]" : 'Hello'
this.println(message)
}
}
All the statements we wrote in our script (GreeterScript.groovy) are available within the run
method in the generated class. This method gets invoked from the main
method. You might be wondering how does the run
method get the values of args
since we don't see the method run
defining any parameters! Take a look at the constructor that accepts groovy.lang.Binding
. The main
method calls runScript
method of org.codehaus.groovy.runtime.InvokerHelper
with two arguments. The first argument is the script class, which is the only class we have at the moment. The second argument is args
containing our command-line arguments. runScript
creates an instance of groovy.lang.Binding
by passing args
to the constructor call. groovy.lang.Binding
will store the value passed to it through constructor with the key 'args'. Thus the run
method gets the command-line arguments in a variable args
. To validate our findings, let's modify the script as follows.
GreeterScript.groovy
String message = args.length > 0 ? "Hello ${args[0]}" : "Hello"
println message
println binding.variables
When we run the code, we see how args
variable is available within the Binding
.
➜ demo groovy GreeterScript.groovy
Hello
[args:[]]
➜ demo groovy GreeterScript.groovy Groovy
Hello Groovy
[args:[Groovy]]
At this point, you might have the following two questions.
- How did the
run
method accessargs
which is present within the binding without specifying the exact path to it? - Wouldn't it have been much simpler if the entire script contents were put inside the
main
method in the generated class?
Let's explore them in another post.