Asynchronous Method calls with Groovy: @Async AST
Join the DZone community and get the full member experience.
Join For FreeAt work, I needed to create a very simple background job, without any concern about what I could get back, because mostly all the hard work was just batch processing and persistence, and all exceptions or roll-back concerns were already taking care of.
At the beginning I used a very simple way to call my background job, using Java's: Executors.newSingleThreadExecutor()
void myBackgroundJob() {
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
//My Background Job
}
});
}
And it worked great, just what I needed. Using Groovy facilitate even more the way to create a new Background job, as simple as:
def myBackgroundJob() {
Thread.start {
//My Background Job
}
}
Then, after this simple way to send something into the background, I decided to create a new AST in groovy, that remove the need to remember or copy and paste the same logic.
I created two annotations that help to identify the class and the methods that are going to be put into a new Thread.
One for the Class:
package async
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.*
import xml.ToXmlTransformation
@Retention (RetentionPolicy.SOURCE)
@Target ([ElementType.TYPE])
@GroovyASTTransformationClass (["async.AsyncTransformation"])
public @interface Asynchronous { }
And the other for the Method:
package async
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.*
import async.AsyncTransformation
@Retention (RetentionPolicy.SOURCE)
@Target ([ElementType.METHOD])
@GroovyASTTransformationClass (["async.AsyncTransformation"])
public @interface Async { }
then the Asynchronous Transformation, using the AstBuilder().buildFromString(). Here I combined a GroovyInterceptable to connect the method being call with the AST transformation to wrapped with the Thread logic.
package async
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.transform.*
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.expr.ArgumentListExpression
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) //CompilePhase.SEMANTIC_ANALYSIS
class AsyncTransformation implements ASTTransformation{
void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
if (!astNodes ) return
if (!astNodes[0] || !astNodes[1]) return
if (!(astNodes[0] instanceof AnnotationNode)) return
if (astNodes[0].classNode?.name != Asynchronous.class.name) return
def methods = makeMethods(astNodes[1])
if(methods){
astNodes[1]?.interfaces = [ ClassHelper.make(GroovyInterceptable, false), ] as ClassNode []
astNodes[1]?.addMethod(methods?.find { it.name == 'invokeMethod' })
}
}
def makeMethods(ClassNode source){
def methods = source.methods
def annotatedMethods = methods.findAll { it?.annotations?.findAll { it?.classNode?.name == Async.class.name } }
if(annotatedMethods){
def expression = annotatedMethods.collect { "name == \"${it.name}\"" }.join(" || ")
def ast = new AstBuilder().buildFromString(CompilePhase.INSTRUCTION_SELECTION, false, """
package ${source.packageName}
class ${source.nameWithoutPackage} implements GroovyInterceptable {
def invokeMethod(String name, Object args){
if(${expression}){
Thread.start{
def calledMethod = ${source.nameWithoutPackage}.metaClass.getMetaMethod(name, args)
calledMethod?.invoke(this, args)
}
}else{
def calledMethod = ${source.nameWithoutPackage}.metaClass.getMetaMethod(name, args)?.invoke(this,args)
}
}
}
""")
ast[1].methods
}
}
}
The example:
package async
@Asynchronous
class Sample{
String name
String phone
@Async
def expensiveMethod(){
println "[${Thread.currentThread()}] Started expensiveMethod"
sleep 15000
println "[${Thread.currentThread()}] Finished expensiveMethod..."
}
@Async
def otherMethod(){
println "[${Thread.currentThread()}] Started otherMethod"
sleep 5000
println "[${Thread.currentThread()}] Finished otherMethod"
}
}
println "[${Thread.currentThread()}] Start"
def sample = new Sample(name:"AST EXample",phone:"1800-GROOVY")
sample.expensiveMethod()
sample.otherMethod()
println "[${Thread.currentThread()}] Finished"
Final Notes:
As you can see on the example I need to have the Asynchronous annotation on the class still. It could be better without it and just annotate the methods, something like the Groovy's SynchronizedASTTransformation. If you have any idea to complement this small example, please
clone the source code [here], and let me know what you think.
I could used the @javax.ejb.Asynchronous or the Spring's @org.springframework.scheduling.annotation.Async, but I only needed a very simple solution without any other configuration or library inclusion.
The remain logic here could be play more with multi threading and expect some results like: java.util.concurrent.Future<V> and its java.util.concurrent.Future<V>.get() method or maybe integrated with another frameworks like Spring.
Source: [Here]
Opinions expressed by DZone contributors are their own.
Comments