blob: 101d2d288e9d886cdaacf2219d773f9c5ce58644 [file] [log] [blame]
:index-group: EJB
:jbake-type: page
:jbake-status: status=published
= 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 http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Future.html[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: http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Future.html[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
[source,java]
----
@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);
}
}
}
----
== Teste
[source,java]
----
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);
}
}
----
[source,console]
----
-------------------------------------------------------
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 'javax.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 de
`JobProcessor`. 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
http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executor.html[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ância `JobProcessor`, ele pegará o valor de retorno e o configurará
o `Future` 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 http://download.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html[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
link:../simple-mdb/README.html[simple-mdb]