Статьи

Вызов асинхронного метода с Groovy: @Async AST

На работе мне нужно было создать очень простую фоновую работу, не беспокоясь о том, что я смогу получить обратно, потому что в основном вся тяжелая работа была только пакетной обработкой и сохранением, а все исключения или проблемы отката уже решались.

В начале я использовал очень простой способ вызова своей фоновой работы, используя Java: Executors.newSingleThreadExecutor ()

void myBackgroundJob() {
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
//My Background Job
}
});
  }

 И это работало отлично, как раз то, что мне было нужно. Использование Groovy упрощает создание нового фонового задания, такого как:

def myBackgroundJob() {
Thread.start {
//My Background Job
}
}

 Затем, после этого простого способа отправить что-то в фоновый режим, я решил создать новый AST в groovy, который избавит от необходимости запоминать или копировать и вставлять ту же логику.

Я создал две аннотации, которые помогают идентифицировать класс и методы, которые будут помещены в новый поток. 

Один для класса:

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 { }

 И другой для метода:

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 { }

 затем асинхронное преобразование с использованием AstBuilder (). buildFromString (). Здесь я объединил GroovyInterceptable, чтобы связать вызываемый метод с преобразованием AST с логикой Thread.

 

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
         }
    }

}

 Пример:

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"

 Заключительные замечания:

Как вы можете видеть на примере, мне все еще нужно иметь асинхронную аннотацию для класса. Может быть лучше без него и просто аннотировать методы, что-то вроде Groovy’s SynchronizedASTTransformation . Если у вас есть идея дополнить этот небольшой пример, пожалуйста,
клонируйте исходный код [ здесь ] и дайте мне знать, что вы думаете.

Я мог бы использовать @ javax.ejb.Asynchronous или Spring’s @ org.springframework.scheduling.annotation.Async , но мне нужно было только очень простое решение без какой-либо другой конфигурации или включения библиотеки.

Оставшуюся логику здесь можно было бы поиграть больше с многопоточностью и ожидать некоторых результатов, таких как: java.util.concurrent.Future <V> и егоjava.util.concurrent.Future <V> .get () или может быть интегрирован с другими фреймворками, такими как Spring.

 Источник: [ Здесь ]