Introduction
Using GroovyClassLoader.parseClass()
, it is possible to create a new Groovy
class dynamically at run-time and use it from a Groovy script or a Java application. GroovyClassLoader.parseClass()
will parse a string
passed to it and attempt to create a Groovy
class. This way, it is possible to add the necessary imports, set the class name and add the fields to the class being created. Groovy
automatically adds field getters and setters - if a field name is field1
, getField1()
and setField1()
are generated. Adding methods is also possible this way, but it can also be done later by assigning closures to the created class MetaClass
. This is convenient because parsing is avoided. Closure parameters become method parameters.
Creating the Class
The ClassBuilder.groovy
file is as follows:
package javainterop2
class ClassBuilder {
GroovyClassLoader loader
String name
Class cls
def imports
def fields
def methods
def ClassBuilder(GroovyClassLoader loader) {
this.loader = loader
imports = []
fields = [:]
methods = [:]
}
def setName(String name) {
this.name = name
}
def addImport(Class importClass) {
imports << "${importClass.getPackage().getName()}" +
".${importClass.getSimpleName()}"
}
def addField(String name, Class type) {
fields[name] = type.simpleName
}
def addMethod(String name, Closure closure) {
methods[name] = closure
}
def getCreatedClass() {
def templateText = '''
<%imports.each {%>import $it\n <% } %>
class $name
{
<%fields.each {%> $it.value $it.key \n<% } %>
}
'''
def data = [name: name, imports: imports, fields: fields]
def engine = new groovy.text.SimpleTemplateEngine()
def template = engine.createTemplate(templateText)
def result = template.make(data)
cls = loader.parseClass(result.toString())
methods.each {
cls.metaClass."$it.key" = it.value
}
return cls
}
}
Using the Class from Groovy
The test.groovy file in the same package:
package javainterop2
import java.util.Calendar
def builder = new ClassBuilder(this.class.classLoader)
builder.setName("MyClass");
builder.addImport(Calendar)
builder.addField('field1', Integer)
builder.addField('field2', Integer)
builder.addMethod('sum') { field1 + field2 }
builder.addMethod('product') { field1 * field2 }
builder.addMethod('testCalendar') {
println Calendar.getInstance().getTime()
}
Class myClass = builder.getCreatedClass()
def myInstance = myClass.newInstance()
myInstance.field1 = 1
myInstance.field2 = 2
println myInstance.sum()
println myInstance.product()
myInstance.setField2(1500)
println myInstance.getField2()
myInstance.testCalendar()
The class created by GroovyClassLoader
is stored in a variable and can be instantiated using myClass.newInstance()
. field2
can be accessed directly or using its setter setField2()
. The java.util.Calendar
class is imported and used in the class' testCalendar()
method.
Using the Class from Java
Using a dynamically generated class from Java is not so straightforward. The class can be passed from Groovy
to Java as a variable. test1.groovy creates another Groovy
class:
package javainterop2
static Class getDynamicClass(ClassLoader loader) {
def builder = new ClassBuilder(new GroovyClassLoader(loader))
builder.setName('AnotherClass')
builder.addField('field1', Integer)
builder.addField('field2', Integer)
builder.addMethod('sum') { return field1 + field2 }
return builder.getCreatedClass()
}
and in Java:
package javainterop2;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyShell;
import java.io.File;
import java.io.IOException;
import org.codehaus.groovy.control.CompilationFailedException;
public class Interop {
public static void main(String[] args) {
try {
File file = new File("src/javainterop2/test1.groovy");
GroovyShell shell = new GroovyShell();
Class<?> AnotherClass = (Class<?>) shell.parse(file).invokeMethod(
"getDynamicClass", GroovyShell.class.getClassLoader());
System.out.println(AnotherClass);
try {
GroovyObject o = (GroovyObject) AnotherClass.newInstance();
o.setProperty("field1", 1);
o.setProperty("field2", 2);
Object[] arguments = {};
System.out.println(o.invokeMethod("sum", arguments));
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
} catch (CompilationFailedException | IOException e) {
e.printStackTrace();
}
}
}
test1.groovy is parsed by GroovyShell
and getDynamicClass()
is invoked to get the class variable. The new class instance is cast to GroovyObject
and GroovyObject.setProperty()
, GroovyObject.getProperty()
and GroovyObject.invokeMethod()
are used to manipulate the instance.
The Example Project
I added the Eclipse project I used for testing that contains all the files listed above. The project references groovy-all-2.2.2.jar which is located in the project's lib directory. The file is a part of the standard Groovy distribution (in the groovy/2.2.2/embeddable directory on my machine) and is used for invoking Groovy scripts from Java. Eclipse is Kepler Service Release 2.