Reputation: 673
I want to use a 'Util' groovy script inside another groovy script. I don't want to load 'Util' class inside my 'main' groovy script every time. So using evaluate or GroovyShell don't fit my case.
My java application fetches the 'main' groovy script body from a database, parse it and call test() method from 'main' script every time.
java code :
GroovyShell groovyShell = new GroovyShell();
Script parsedScript = groovyShell.parse(scriptBody);
ResultPojo result = (ResultPojo) parsedScript.invokeMethod("test", null);
'main' script
public int test(){
// this will not work at the moment
int result = GroovyUtils.sum();
return result;
}
A 'Util' class will be located in the database too. 'Util' classes will be somehow loaded on application startup and they will be reloaded every X minutes.
class GroovyUtils{
static int sum() {
return 2+1;
}
}
Like i said i don't want to 'parse' the GroovyUtils class inside 'main' script because this is time costly.
Ideally i want to import GroovyUtils script when i need it.
import groovy.GroovyUtils;
public int test(){
int result = GroovyUtils.sum();
return result;
}
But in order to import the script, the script need to be saved in the same folder that the java application runs. The java application is deployed on a remote application server in .war format.
Can i somehow load GroovyUtils dynamically to CLASSPATH without saving it, so i can import it from my 'main' script?
Any suggestions? My main concerns is speed and reloadability.
Upvotes: 1
Views: 4225
Reputation: 28564
if you'd like to create a delivery process through the database you can do it by extending GroovyClassLoader and implementing public Class loadClass(name, lookupScriptFiles, preferClassOverScript, resolve) method that will search classes in some table in a database.
Let me simplify your goal and exclude database.
There is a standard behavior of classloaders: search and load classes among the classpath
The GroovyClassLoader allows to add new paths to a classpath at runtime, so it will search additionally classes in specified folder or jar file.
classloader keeps parsed classes in memory and groovy classloader provides protected method to remove class definition by name: removeClassCacheEntry(java.lang.String)
and finally example:
/myprj/classes/util/MyClass.groovy
package util
class MyClass{
def echo(msg){ println msg }
}
code to run main script
//create shell and init classloader just once
GroovyShell gs = new GroovyShell()
gs.getClassLoader().addClasspath("/myprj/classes/")
//forces classloader to recompile on file change
//this is alternative to removeClassCacheEntry
//but in some specific cases this reload will not work
gs.getClassLoader().setShouldRecompile(true)
Script script = gs.parse('''
import util.MyClass
new MyClass().echo("hello world")
''')
script.run() // prints 'hello world'
//removeClassCacheEntry is alternative to setShouldRecompile
//you can use it to remove compiled class from this classloader
println gs.getClassLoader().getLoadedClasses() // outputs util.MyClass, and Script1
gs.getClassLoader().removeClassCacheEntry("util.MyClass")
println gs.getClassLoader().getLoadedClasses() // outputs Script1
returning to the database: you could have a daemon thread that scans database for groovy code changes and exports modified sources into a folder that was defined as additional classpath and triggers removeClassCacheEntry
for the classloader. So, next access to removed class will force to parse it by GroovyClassLoader.
NOTE: by using dynamic class loading you could have situation when two versions of same class present in memory and they will not be comparible and assignable to each other. So, you could have the error like:
could not assign MyClass to MyClass
Upvotes: 2