@Singleton
public class JobProcessor {
@Asynchronous
@Lock(READ)
@AccessTimeout(-1)
public Future<String> addJob(String jobName) {
// Finja que este trabalho leva um tempo
doSomeHeavyLifting();
// Devolva nosso resultado
return new AsyncResult<String>(jobName);
}
private void doSomeHeavyLifting() {
try {
Thread.sleep(SECONDS.toMillis(10));
} catch (InterruptedException e) {
Thread.interrupted();
throw new IllegalStateException(e);
}
}
}
Métodos Assíncronos
A anotação @Asynchronous
foi introduzida no EJB 3.1 como um simples meio
de criar processamento assícrono.
Toda vez que um método anotado com @Asynchronous
é invocado por qualquer um que
vai imediatamente retornar independentemente de quanto tempo o método realmente leva.
Cada invocação retorna um objeto Future que essencialmente inicia vazio e terá mais tarde o seu valor preenchido pelo contêiner quando o método relacionado é chamado concluir.
Retornar um objeto Future
não é necessário e os métodos @ Asynchronous
podem retornar void
.
Exemplo
Aqui, no JobProcessorTest
,
final Future<String> red = processor.addJob("red");
prossegue para a
próxima declaração,
final Future<String> orange = processor.addJob("orange");
sem aguardar pelo método addJob()
para completar. E depois poderíamos
perguntar pelo resultado usando o método Future <?>. get ()
como
assertEquals("blue", blue.get());
Aguardar que o processamento seja concluído (se ainda não estiver concluído) e obter o resultado. Se você não se importar com o resultado, você poderia simplesmente ter seu método assíncrono como um método vazio.
Veja a documentação para saber mais: Future
Uma Future representa o resultado de um cálculo assíncrono. Métodos são fornecidos para verificar se o cálculo está completo, para aguardar a conclusão, e para recuperar o resultado do cálculo. O resultado pode somente ser recuperado usando o método get quando o cálculo estiver completado, bloqueando se necessário, até que esteja pronto. O cancelamento é realizada pelo método de cancelamento. Método adicionais são fornecidos para determinar se a tarefa completou normalmente ou foi cancelada. Uma vez o cálculo foi concluído, o cálculo não pode ser cancelado. Se você gostaria de usar um Future para causa de cancelabilidade mas não fornecer um resultado utilizável, você pode declarar tipos do formulário Future <?> e retornar null como resultado da tarefa subjacente.
O código
Teste
public class JobProcessorTest extends TestCase {
public void test() throws Exception {
final Context context = EJBContainer.createEJBContainer().getContext();
final JobProcessor processor = (JobProcessor) context.lookup("java:global/async-methods/JobProcessor");
final long start = System.nanoTime();
// Enfileirar um monte de trabalho
final Future<String> red = processor.addJob("red");
final Future<String> orange = processor.addJob("orange");
final Future<String> yellow = processor.addJob("yellow");
final Future<String> green = processor.addJob("green");
final Future<String> blue = processor.addJob("blue");
final Future<String> violet = processor.addJob("violet");
// Aguarde o resultado - 1 minuto de trabalho
assertEquals("blue", blue.get());
assertEquals("orange", orange.get());
assertEquals("green", green.get());
assertEquals("red", red.get());
assertEquals("yellow", yellow.get());
assertEquals("violet", violet.get());
// Quanto tempo levou?
final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);
// A execução deve ser em torno de 9 a 21 segundos
// O tempo de execução depende do número de encadeamentos disponíveis para execução assíncrona.
//No melhor dos casos, é 10s mais algum tempo de processamento mínimo.
assertTrue("Expected > 9 but was: " + total, total > 9);
assertTrue("Expected < 21 but was: " + total, total < 21);
}
}
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.superbiz.async.JobProcessorTest
INFO - ********************************************************************************
INFO - OpenEJB http://tomee.apache.org/
INFO - Startup: Wed Feb 27 12:46:11 BRT 2019
INFO - Copyright 1999-2018 (C) Apache OpenEJB Project, All Rights Reserved.
INFO - Version: 8.0.0-SNAPSHOT
INFO - Build date: 20190227
INFO - Build time: 04:12
INFO - ********************************************************************************
INFO - openejb.home = /home/soro/git/apache/tomee/examples/async-methods
INFO - openejb.base = /home/soro/git/apache/tomee/examples/async-methods
INFO - Created new singletonService org.apache.openejb.cdi.ThreadSingletonServiceImpl@22f71333
INFO - Succeeded in installing singleton service
INFO - Using 'jakarta.ejb.embeddable.EJBContainer=true'
INFO - Cannot find the configuration file [conf/openejb.xml]. Will attempt to create one for the beans deployed.
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Creating TransactionManager(id=Default Transaction Manager)
INFO - Creating SecurityService(id=Default Security Service)
INFO - Found EjbModule in classpath: /home/soro/git/apache/tomee/examples/async-methods/target/classes
INFO - Beginning load: /home/soro/git/apache/tomee/examples/async-methods/target/classes
INFO - Configuring enterprise application: /home/soro/git/apache/tomee/examples/async-methods
INFO - Auto-deploying ejb JobProcessor: EjbDeployment(deployment-id=JobProcessor)
INFO - Configuring Service(id=Default Singleton Container, type=Container, provider-id=Default Singleton Container)
INFO - Auto-creating a container for bean JobProcessor: Container(type=SINGLETON, id=Default Singleton Container)
INFO - Creating Container(id=Default Singleton Container)
INFO - Configuring Service(id=Default Managed Container, type=Container, provider-id=Default Managed Container)
INFO - Auto-creating a container for bean org.superbiz.async.JobProcessorTest: Container(type=MANAGED, id=Default Managed Container)
INFO - Creating Container(id=Default Managed Container)
INFO - Using directory /tmp for stateful session passivation
INFO - Enterprise application "/home/soro/git/apache/tomee/examples/async-methods" loaded.
INFO - Assembling app: /home/soro/git/apache/tomee/examples/async-methods
INFO - Jndi(name="java:global/async-methods/JobProcessor!org.superbiz.async.JobProcessor")
INFO - Jndi(name="java:global/async-methods/JobProcessor")
INFO - Existing thread singleton service in SystemInstance(): org.apache.openejb.cdi.ThreadSingletonServiceImpl@22f71333
INFO - Some Principal APIs could not be loaded: org.eclipse.microprofile.jwt.JsonWebToken out of org.eclipse.microprofile.jwt.JsonWebToken not found
INFO - OpenWebBeans Container is starting...
INFO - Adding OpenWebBeansPlugin : [CdiPlugin]
INFO - All injection points were validated successfully.
INFO - OpenWebBeans Container has started, it took 316 ms.
INFO - Created Ejb(deployment-id=JobProcessor, ejb-name=JobProcessor, container=Default Singleton Container)
INFO - Started Ejb(deployment-id=JobProcessor, ejb-name=JobProcessor, container=Default Singleton Container)
INFO - Deployed Application(path=/home/soro/git/apache/tomee/examples/async-methods)
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 23.491 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Como funciona debaixo dos panos
Sob os panos, o que faz esse trabalho é:
-
O
JobProcessor
que é o chamador vê que não é realmente uma instância deJobProcessor
. Pelo contrário, é uma subclasse ou proxy que tem todos os métodos sobrescrito. Métodos que devem ser assíncronos são manipulados diferentemente. -
Chamadas para um método assíncrono simplesmente resultam em um
Runnable
sendo criado envolve o método e os parâmetros que você deu. Este runnable é dado a um Executor que é simplesmente uma fila de trabalho anexada a um conjunto de encadeamentos. -
Depois de adicionar o trabalho à fila, a versão com proxy do método retorna uma implementação de
Future
que está ligada ao` Runnable` que agora está esperando na fila. -
Quando o
Runnable
finalmente executa o método no real Na instânciaJobProcessor
, ele pegará o valor de retorno e o configurará oFuture
tornando-o disponível para o chamador.
Importante notar que o objeto AsyncResult
o` JobProcessor`
retornado não é o mesmo objeto Future
que o chamador está segurando.
Seria legal se o JobProcessor
real pudesse retornar` String` e
a versão do chamador de JobProcessor
poderia retornar` Future <String> `,
mas nós não vimos nenhuma maneira de fazer isso sem adicionar mais complexidade.
Então o AsyncResult
é um simples objeto wrapper. O contêiner vai puxar
o String
para fora, lançar o ` AsyncResult`, então colocar o String
em
real Future
que o chamador está segurando.
Para obter progresso ao longo do caminho, simplesmente passe um objeto seguro para thread como AtomicInteger
para o método @ Asynchronous
e ter o código do bean periodicamente atualizado
com o percentual completo.
Exemplos relacionados
Para processamento assíncrono complexo, a resposta do JavaEE é
@ MessageDrivenBean
. Dê uma olhada no exemplo
simple-mdb