На работе мне нужно было создать очень простую фоновую работу, не беспокоясь о том, что я смогу получить обратно, потому что в основном вся тяжелая работа была только пакетной обработкой и сохранением, а все исключения или проблемы отката уже решались.
В начале я использовал очень простой способ вызова своей фоновой работы, используя 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.
Источник: [ Здесь ]