jeudi 15 avril 2010

EJB 3.1 Asynchronous Session Beans


Before EJB 3.1, if you want to execute an asynchronous processing you had to use JMS and a MDB, not so easy and rather heavy in most cases.
Now with EJB 3.1, you can use a simple session EJB with the @Asynchronous annotation on the method which must be called asynchronously.
@Stateless
@Remote(HelloEjbAsynchronousRemote.class)
public class HelloEjbAsynchronous implements HelloEjbAsynchronousRemote {

    @Asynchronous
    @Override
    public Future<String> ejbAsynchronousSayHello(String name){
If your method has a return value, your method has to return an AsyncResult object which is an implementation of Future.
return new AsyncResult<String>("Hello "+name);
For the client, you just have to call the remote ejbAsynchronousSayHello by using the new portable global JNDI names.
The EJB container returns the control to the client immediately, in my case with a Future object in return, and then executes the method.
HelloEjbAsynchronousRemote ha = 
            (HelloEjbAsynchronousRemote)ic.lookup("java:global/EjbAsynchronous/HelloEjbAsynchronous");
            Future future = ha.ejbAsynchronousSayHello("Patrick");
The client can retrieve the result value with Future.get() and can get the state of the processing with Future.isDone().
String ret = (String)future.get();

Full source code of the EJB - HelloEjbAsynchronous.java
package fr.paddy.ejb31;

import java.util.Date;
import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.Remote;
import javax.ejb.Stateless;

@Stateless
@Remote(HelloEjbAsynchronousRemote.class)
public class HelloEjbAsynchronous implements HelloEjbAsynchronousRemote {

    @Asynchronous
    @Override
    public Future<String> ejbAsynchronousSayHello(String name){
        System.out.println(new Date().toString()+" - Begin - HelloEjbAsynchronos->ejbAsynchronousSayHello "+name);

        try{
           Thread.sleep(5*1000);
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println(new Date().toString()+" - End - HelloEjbAsynchronos->ejbAsynchronousSayHello "+name);

        return new AsyncResult<String>("Hello "+name);
        
    }
}
The ejbAsynchronousSayHello method, which is declared asynchronous, just wait 5 seconds to simulate a long processing and return the String   "Hello "+name, the variable name being passed as parameter.

Full source code of the remote interface - HelloEjbAsynchronousRemote.java
package fr.paddy.ejb31;

import java.util.concurrent.Future;

public interface HelloEjbAsynchronousRemote {    
    public Future<String> ejbAsynchronousSayHello(String name);
}


Full source code of the EJB Client - ClientMain.java
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import fr.paddy.ejb31.HelloEjbAsynchronousRemote;
import java.util.Date;

public class ClientMain {

    public static void main(String[] args) {
        Properties props = new Properties();
        props.setProperty("java.naming.factory.initial",
                             "com.sun.enterprise.naming.SerialInitContextFactory");
        props.setProperty("java.naming.factory.url.pkgs",
                             "com.sun.enterprise.naming");
        props.setProperty("java.naming.factory.state",
                             "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
        props.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
        props.setProperty("org.omg.CORBA.ORBInitialPort", "3700");

        try{
            InitialContext ic = new InitialContext(props);
            String ret="";

            HelloEjbAsynchronousRemote ha =
            (HelloEjbAsynchronousRemote)ic.lookup("java:global/EjbAsynchronous/HelloEjbAsynchronous");
            Future future = ha.ejbAsynchronousSayHello("Patrick");

            while (!future.isDone()){
                Thread.sleep(1000);
                System.out.println(new Date().toString()+" - I do other things ...");
            }

            ret = (String)future.get();
            System.out.println(new Date().toString()+" - ret : "+ret);

        }catch (NamingException ne){
            ne.printStackTrace();
        }catch (InterruptedException ie){
            ie.printStackTrace();
        }catch (ExecutionException ee){
            ee.printStackTrace();
        }
    }
}

Get the Netbeans projects:  EJB , Client
Because of this issue, you must use GlassFish V3.0.1 promoted build 12 to test this example.
Or used this workaround
while (!future.isDone()){
                Thread.sleep(1000);
                System.out.println(new Date().toString()+" - I do other things ...");
                // System.out.println("futur.isDone"+future.isDone());
                // just a hack, because at the moment future.isDone doesn't work !
                try{
                    Object o = future.get(10,TimeUnit.MILLISECONDS);
                }catch (TimeoutException te){
                   // te.printStackTrace();
                }
                // end of the hack
                // System.out.println("futur.isDone"+future.isDone());
            }

1 commentaire:

Michał Huniewicz a dit…

What if a @Asynchronous @Stateless bean has a @Stateful EJB injected? Is it a different instance every time (i.e. is it guaranteed?)?