Compile GroovyScript at Runtime and allow caching of compiled source to avoid recompilation at Runtime using GroovyClassLoader
In some cases we need mechanism to provide compilation of groovy script at runtime. But at the same time we need to make sure it doesn’t consume the JVM to perform compilation every time when we try to access that script. Hence to perform this we need to do the following task :
- Compile Groovy Script at Runtime.
- Cache the compiled source to avoid recompilation.
- Make sure script get recompiled when any changes are performed over the script.
Interestingly GroovyClassLoader provide loading of groovy script and parsing of class and GroovyCodeSource provide caching of source so that we don’t need to recompile it every time until and unless there are any changes over script from last compilation.
Solution for point 1 and 2 :
GroovyCodeSource setCachaeble(true) led to caching of compiled script, and when class loader parseClass is being called it check for source is set to cacheable or not, if yes then it fetch the source from cache instead of performing recompilation.
Solution for point 3 :
Important thing to note down : Argument codeSourceName in getScriptClass(…) method.
When GroovyClassLoader parseClass is being called then it will look for whether resource is cacheable or not, if yes then it will use the codeSourceName to fetch the cached compiled script from cache. Hence here codeSourceName will act as a key for compiled cached script.
String codeSourceName = "_${someKey}" + "_${scriptHashCode}"
Hence one of the solutions is that you need to generate the hash of GroovyScript and use it as a codeSourceName because whenever there are any changes over the script, the hash got changed for the script which led to recompilation of it, as GroovyClassLoader won’t able to find the script with that hash over a cache.
Edit : 1
In Solution 1, as we are providing example while creating new instance of GroovyClassLoader, at that time I was doing implementation and I didn’t dig deep to see the sourceCache was not grails global level cache it was Map maintained over GroovyClassLoader, hence it doesn’t benefit us with cache over next call of getScriptClass as it will reinitialize the cache due to which we changed the implementation and use single bean of GroovyClassLoader.
We have created our CustomGrailsAwareClassLoader singleton bean which extends GrailsAwareClassLoader which further extends GroovyClassLoader. So that we can benefit from the cache for the script that was compiled earlier.
You create a new GroovyClassLoader each time getScriptClass() is called, doesn’t this also create a new cache? Wouldn’t you need to create one GroovyClassLoader for the class that contains the method getScriptClass() in order to reap the benefits of the cache?
Hi Vincent,
Thanks for the inputs. Yes you are right I initially thought it was grails global level cache and didn’t dig much, by the time I was writing the blog. But later we realized that cache was specific to GroovyClassLoader instance, hence we changed our implementation to singleton bean of GroovyClassLoader. Thanks again for reminding me to update the blog. Please refer the updated blog.
@Tarun Is it possible to compile and ensure that the script don’t have any compilation errors for example, a missingpropertyException which could be introduced to script by adding some random characters or string.