Chapter 6. JBoss EJB 3.0 extensions

JBoss provides a few extensions to the EJB 3.0 spec. This chapter describes those features here.

6.1. @Service EJBs

An extension offered by JBoss EJB 3.0 is the notion of a @org.jboss.annotation.ejb.Service annotated bean. They are singleton beans and are not pooled, so only one instance of the bean exists in the server. They can have both @Remote and @Local interfaces so they can be accessed by java clients. When different clients look up the interfaces for @Service beans, all clients will work on the same instance of the bean on the server. When installing the bean it gets given a JMX ObjectName in the MBean server it runs on. The default is

jboss.j2ee:service=EJB3,name=<Fully qualified name of @Service bean>,type=service

You can override this default ObjectName by specifying the objectName attribute of the @Service annotation.

6.1.1. @org.jboss.annotation.ejb.Management interface

In addition to supporting @Local and @Remote interfaces, a @Service bean can also implement an interface annotated with @Management . This interface will wrap the bean as an MBean and install it in the JBoss MBean Server. The operations and attributes defined in the @Management interfaces become MBean operations and attributes for the installed MBean.. The underlying bean instance is the same as the one accessed via the @Local or @Remote interfaces.

6.1.2. Lifecycle Management and dependencies

Just as for normal MBeans running in JBoss, @Service lifecycle management beans support lifecycle management. This involves

  • Lifecycle Methods.
  • Dependencies.

6.1.2.1. Lifecycle Methods

Your @Management interface may contain methods with the following signatures

   void create() throws Exception;
   void start() throws Exception;
   void stop();
   void destroy();
                     

Each of these methods corresponds to when the MBean enters a particular state, so you can handle that. You do not need to include any of these methods, and you can pick and choose which ones you want to use. A description of the MBean states these methods correspond to:

  • create() - called by the server when the service is created and all the services it depends upon have been created too. At this point the service (and all the services it depends on) is installed in the JMX server, but is not yet fully functional.
  • start() - called by the server when the service is started and all the services it depends upon have been started too. At this point the service (and all the services it depends on) is fully functional..
  • stop() - called by the server when the service is stopped. At this point the service (and all the services that depend on it) is no longer fully operational.
  • destroy() - called by the server when the service is destroyed and removed from the MBean server. At this point the service (and all the services that depend on it) are destroyed.

6.1.2.2. Dependencies

We mentioned dependencies between MBeans in the previous section. You can specify what MBeans you depend on by using the org.jboss.annotation.ejb.Depends annotation. This will also work for "proper" EJBs, i.e. ones annotated with @Stateful, @Stateless and @MessageDriven. The Depends annotation-type takes an array of the String representation of the JMX ObjectNames of the service we depend on.

               @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) 
               @Retention(RetentionPolicy.RUNTIME)
               public @interface Depends
               {
                  String[] value();
               }
               

It can either be used on class level, in which case it simply means "I depend on these services". It can also be used on fields or setters in the bean class that take a type of a @Management annotated interface. In addition to the meaning of annotating at class level it will inject the @Management interface of the service we depend on.

6.1.3. Example

First we define a @Management interface

package org.jboss.tutorial.service.bean;

import org.jboss.annotation.ejb.Management;

@Management
public interface ServiceOneManagement
{
   void setAttribute(int attribute);
   int getAttribute();
   String sayHello();

   void create() throws Exception;
   void start() throws Exception;
   void stop();
   void destroy();
}          
           

We also have a Remote interface

package org.jboss.tutorial.service.bean;

public interface ServiceOneRemote
{
   public void setAttribute(int attribute);
   public int getAttribute();
}           
           

And fnally we have a @Service annotated bean implementing our interfaces

package org.jboss.tutorial.service.bean;

import org.test.OtherServiceManagement;

import javax.ejb.Remote;
import org.jboss.annotation.ejb.Service;
import org.jboss.annotation.ejb.Depends;


@Service (objectName = "jboss:custom=Name")
@Remote(ServiceOneRemote.class)
@Depends ({"jboss:service=someService"})
public class ServiceOne implements ServiceOneRemote, ServiceOneManagement
{
   int attribute;
   
   @Depends({"jboss.j2ee:service=EJB3,name=org.test.OtherService,type=service"})
   OtherServiceManagement other;

   public void setAttribute(int attribute)
   {
      this.attribute = attribute;
   }

   public int getAttribute()
   {
      return this.attribute;
   }

   public String sayHello()
   {
      return "Hello from service One";
   }

   // Lifecycle methods
   public void create() throws Exception
   {
      System.out.println("ServiceOne - Creating");
   }

   public void start() throws Exception
   {
      System.out.println("ServiceOne - Starting");
   }

   public void stop()
   {
      System.out.println("ServiceOne - Stopping");
   }

   public void destroy()
   {
      System.out.println("ServiceOne - Destroying");
   }
}
           

This bean is accessible from remote clients via the ServiceOneRemote interface. The ServiceOneManagement interface defines the operations to be made available via JMX. It specfies all the lifecycle methods so we will get notified when this bean is created, started, stopped and destroyed. Our bean uses a custom object name, so it will be installed in the MBean server under the name jboss:custom=Name. At bean level we have specified that it depends on a service with the name jboss:service=someService, so that service must be started before our bean can start. Also, we have specified that we depend on another JMX service called jboss.j2ee:service=EJB3,name=org.test.OtherService,type=service and that we want to inject an MBean Proxy implementing the org.test.OtherServiceManagement interface into the other field. For a more complete example, take a look at the tutorial.

6.2. Message Driven POJOs

Doco not complete yet. See tutorial for more information.

6.3. Asynchronous invocations

In JBoss EJB 3.0, you can convert a reference to a local or remote session bean into a reference that will invoke asynchronously on the EJB. This is called obtaining an asychronous proxy or interface. asynchronous interface. Methods called on the asynchronous proxy will be executed asynchronously, and the results can be obtained later on. You obtain an asynchronous proxy by invoking on the org.jboss.ejb3.asynchronous.Asynch.getAsynchronousProxy static method. You pass

         package org.jboss.ejb3.asycnhronous;
         
         public class Asynch
         {
             public static <T> T getAsynchronousProxy(T ejbRef) {...}

             public static Future getFutureResult(Object asynchProxy)
         }
         
      

The asynchronous proxy created will implement the same local/remote interfaces as the original proxy. When you invoke on a method of the asynch proxy, the method will be invoked in the background. If the method has a return value, the value returned by the method will be null or zero depending on the return type. This will not be the true return value of the asynch method so you can just throw this away. To obtain the real result you must obtain org.jboss.aspects.asynch.Future object. This Future object works much the same way a java.util.concurrent.Future object does.

Let us look at an example. We have the following session bean.

         package org.acme.test;

         @javax.ejb.Remote
         public interface Test
         {
            int perfomHeavyOperation(int i);
            String echo(String s);
         }
      
         package org.acme.test;
         
         @javax.ejb.Stateless
         public class TestBean implements Test
         {
            public int perfomHeavyOperation(int i)
            {
               try
               {
                  //Simulate time-consuming operation
                  Thread.sleep(10000);
                  return i;
               }
               catch(Exception e)
               {
                  throw new RuntimeException(e);
               }
            }
            
            public String echo(String s)
            {
               return s;
            }
         }
      

As you can see there is nothing special about the session bean, now let's look at the client code. First we look up the remote interface:

         InitialContext ctx = new InitialContext();
         Test test = (Echo)ctx.lookup(org.acme.test.Test.getName());
         

We now have a reference to the bean's normal interface. Calls done on this interface will execute synchronously. The following call to perfomHeavyOperation() will block the client thread for 10 seconds.

         int i = test.performHeavyOperation(1);
         //i will be 1
         

Now to demonstrate the asynchronous functionality, we the asynchronous proxy.

         Test asynchTest = org.jboss.ejb3.asynchronous.Asynch.getAsynchronousProxy(test);
         

Calls made on the asynchronous interface will return 0 in the case of a simple return type or null in the case of an Object return type. We will see how to obtain the return value further down.

            int j = asynchTest.performHeavyOperation(123);
         //j will be 0
         

The call to perfomHeavyOperation() returns immediately, and our client thread is now free to do other stuff while the business method executes.

         //You can do other stuff in client's thread
         

Now that we have finished doing things in our thread while the business method has been executing on the server, we obtain the Future which will hold the result of our asynchronous invocation.

         Future future = org.jboss.ejb3.asynchronous.Asynch.getFutureResult(asynchTest);
         

It is important to note that you must call getFutureResult() to obtain the future for the last method call. If you call another method on the asynch proxy, then you will lose the previous Future. The asynchronous invocation might not have finished yet (in case the extra things we did in the client code took < 10 seconds), so we check for this here:

         while (!future.isDone())
         {
            Thread.sleep(100);
         }
         

Now that the asynchronous invocation is done, we can obtain its return value from the Future object.

         int ret = (String)future.get();
         //ret will be 123