Session beans

A session EJB is a component that encapsulates specific behavior to process the business logic of an application. Session EJBs have no concept of persistence.

There are three types of session bean available in the EJB specification. They are stateless, stateful, and singleton beans.

Stateless session beans

A stateless session bean is an EJB component that does not maintain state information between client invocations. If you need conversational state and contextual awareness you look to CDI.

Stateless session beans, then, are reserved by design to serve EJB clients that have no requirement to have a conversation. The client just wants to invoke a function on the endpoint and do some useful work on the server, and then carry on with the rest of instructional life.

In EJB 3.x denoting a stateless enterprise bean is very easy. You simply use the @javax.ejb.Stateless annotation on the class. The class can be a Plain Old Java Object (POJO). Let us simply define one now:

Note

Some people believe that stateless session EJB should have been really called Poolable Beans, because these types of EJB are usually allocated from a resource inside the application server.

To declare a stateless session EJB, you add the annotation @Stateless to the class. Here is an example of a customer service EJB:

package je7hb.basic.ejb;
import javax.ejb.Stateless;
import java.util.*;

@Stateless
public class SupportHelpDesk {

    private List<String> agents = Arrays.asList(
        "Agnes","Brian","Harry","Sally","Tom","Pamela",
        "Mark","Wendy","Marcia","Graeme","Pravztik",
        "Hadeep", "Florence", "Robert", "Zoe", "Frank");
    public String getNextAgentName() {
        return agents.get((int)( Math.random() *
               agents.size() ));
    }
}

It does not get easier than this. Annotate the POJO with the stateless annotation on the type. When this EJB is deployed in the application server, it will be assigned an allocation pool size, which can be configured by the system administrator; the Java EE product vendor determines the actual connection pool size. The application server instantiates SupportHelpDesk as a stateless session EJB, and most implementations will wrap a hidden proxy object around the instance. This proxy delegate has opaque container methods and it has a delegation method that invokes one public method getNextAgentName(), which in turn returns a random name of agent.

Let us look at the Gradle build file for this project:

// Same Plug-in imports as before
group = 'com.javaeehandbook.book1'
archivesBaseName = 'ch03-ejb'
version = '1.0'

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        url 'https://maven.java.net/
            content/groups/promoted'
    }
    maven {
        url 'http://repository.jboss.org/
            nexus/content/groups/public'
    }
}

dependencies {
    compile 'org.glassfish.main.extras:
        glassfish-embedded-all:4.0.1-b01'
    compile 'javax:javaee-api:7.0'

    testCompile 'junit:junit:4.11'
    testCompile 'org.jboss.arquillian.junit:
        arquillian-junit-container:1.0.3.Final'
    testCompile 'org.jboss.arquillian.container:
      arquillian-glassfish-embedded-3.1:1.0.0.Final-SNAPSHOT'
}
// Typical Gradle Project - Same as before

Only the dependency management is important as shown in the preceding build file. We are using the Arquillian test framework again, and we explicitly add a dependency on a real application server, GlassFish.

Let's move on to the unit test, which is an Arquillian integration test:

package je7hb.basic.ejb;
/* Other imports omitted */
import javax.ejb.EJB;
import static org.junit.Assert.assertNotNull;

@RunWith(Arquillian.class)
public class SupportHelpDeskTest {
    @Deployment
    public static JavaArchive createDeployment() {
        JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
                .addClasses(SupportHelpDesk.class)
                .addAsManifestResource(
                        EmptyAsset.INSTANCE,
                        ArchivePaths.create("beans.xml"));
        return jar;
    }

    @EJB SupportHelpDesk desk;

    @Test
    public void shouldRetrieveDifferentAgents() {
        System.out.printf("Support help desk = %s
", desk );
        for ( int j=0; j<5; ++j ) {
            String agent  = desk.getNextAgentName();
            System.out.printf("The next agent = %s
",agent);
            assertNotNull(agent);
        }
    }
}

In order to reference the stateless EJB inside the same JVM, and in the same EJB container, we explicitly obtain a reference to the bean using the @EJB. This is similar to the CDI injection, but not quite the same; this injection of a local reference takes place without contextual scope, and the EJB container provides it whenever the EJB bean is created.

The test method shouldRetrieveDifferentAgents() in this unit test executes a simple for-do loop that invokes the EJB service method. The test result prints random agent names to its users. Here is the abbreviated output of the test for study:

 INFO: Created virtual server server
Aug 27, 2013 3:53:40 PM org.apache.catalina.realm.JAASRealm
 setContainer
INFO: Setting JAAS app name glassfish-web
Aug 27, 2013 3:53:40 PM com.sun.enterprise.web.WebContainer
 loadSystemDefaultWebModules
...
INFO: Loading application [test] at [/test]
Aug 27, 2013 3:53:41 PM org.glassfish.deployment.admin.
  DeployCommand execute
INFO: test was successfully deployed in 2,658 milliseconds.
Support help desk = je7hb.basic.ejb.SupportHelpDesk@790ffd6
The next agent = Sally
The next agent = Graeme
The next agent = Pravztik
The next agent = Pamela
The next agent = Florence
PlainTextActionReporterSUCCESS
No monitoring data to report.
...
Aug 27, 2013 3:53:41 PM com.sun.enterprise.v3.server.AppServerStartup stop
INFO: Shutdown procedure finished
Process finished with exit code 0

We will cover referencing of session EJB by clients and the lifecycle later on. Let us move onto the second type of session EJB, the stateful ones.

Concurrency and stateless session EJBs

We have seen how to create a stateless session EJB. The developer may be tempted to think these beans can easily handle concurrency. However, act with extreme caution. An EJB property field will share state Java threads that are passing the component, whereas local variables that are declared inside the method are shared only by the thread context. There are no guarantees on concurrency for a stateless session EJB. This is important to understand, especially when EJBs are instantiated from a fixed size pool of EJBs inside an application server.

A stateless session bean has a lifecycle. The EJB container has the responsibility to allocate a bean to a particular client. By marking a bean as stateless, we humbly declare this bean can be associated with any EJB client. In the end, the code inside method may alter fields of the bean, but we cannot guarantee that the values of those fields will remain the same in the next invocation of the same bean and possibly on a different Java thread.

If you are thinking about concurrency and Java EE then read ahead to Appendix D, Java EE 7 Assorted Topics.

Stateful session beans

A stateful session bean is a session EJB that keeps track of handle to the client caller. In other words, a session EJB maintains state that preserves for each request from a user. Unlike a stateless session bean, a stateful session bean is not shared. The state is only removed once the client terminates. It cannot be reclaimed. The state only remains for the duration of the client-service communication.

One way to think of this idea is that the user has a conversation with the stateful session EJB until it ends or the handle is explicitly released.

We will use an e-commerce shopping cart to demonstrate stateful session EJB. Let us introduce the concept of business interfaces, but first we need an entity to pass between the client and EJB session bean, which we call a customer. Here is a customer POJO shown in the following section:

package je7hb.basic.ejb;
import java.io.Serializable;

public final class Customer implements Serializable {
    private final String firstName;
    private final String lastName;

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() { return firstName;}
    public String getLastName() { return lastName;}

    @Override
    public String toString() {
        return "Customer{" +
          "firstName='" + firstName + ''' +
          ", lastName='" + lastName + ''' +
        '}';
    }

  // equals() and hashCode() methods omitted
}

The Customer value object is serializable in order to support remoting, marshalling, and unmarshalling using RMI-IIOP especially across different JVMs. Marshalling is the process of serializing a Java object to an output stream to create data that obeys a predefined protocol. Unmarshalling is the process of reading information from an input stream with data in the predefined protocol and recreating an active Java object.

There were two types of interfaces for session EJBs: local and remote interfaces. Local interfaces are great for speed, reduced latency, and co-location of server. Remote interfaces are great for network distributions call across two different JVMs.

In Java EE 7, business interfaces can use annotations: @javax.ejb.Remote, which is reserved for session EJB that require a remote call interface, and @java.ejb.Local, which is reserved for session EJB that requires local references.

Let us look at these interfaces, starting with the remote business interface:

package je7hb.basic.ejb;
import javax.ejb.Remote;
import java.util.List;

@Remote
public interface ShoppingCart {
    void initialize(Customer customer);
    void addOrderItem(OrderItem item);
    void removeOrderItem(OrderItem item);
    List<OrderItem> getOrderItems();
    void release();
}

Now, let us look at the local business interface:

package je7hb.basic.ejb;
import javax.ejb.Local;
import java.util.List;

@Local
public interface ShoppingCartLocal {
    void initialize(Customer customer);
    void addOrderItem(OrderItem item);
    void removeOrderItem(OrderItem item);
    List<OrderItem> getOrderItems();
    void release();
}

Both interfaces are fairly straightforward. For this purpose, notice that the contracts are the same for both the remote and local business interface. Note the naming convention for these interfaces, the remote is simply named as a typical class name, say ShoppingCart, whereas the local interface has the suffix Local appended to the class name, say ShoppingCartLocal.

Now let us create the bean implementation of both interfaces in one class:

package je7hb.basic.ejb;

import javax.ejb.Remove;
import javax.ejb.Stateful;
import java.util.ArrayList;
import java.util.List;

@Stateful
public class ShoppingCartBean
implements ShoppingCart, ShoppingCartLocal {
    private List<OrderItem> orderItems = 
      new ArrayList<OrderItem>();
    private Customer customer = null;
    private boolean initialized = false;

    @Override
    public void initialize(Customer customer)  {
        System.out.printf(
          "SCB#initialize() called%s
", this );
        this.customer = customer;
        initialised = true;
    }
    protected void check() {
        if ( !initialised )  {
            throw new RuntimeException(
                    "This shopping cart is not initialised");
        }
    }

    @Override
    public void addOrderItem(OrderItem item) {
      System.out.printf("SCB#addOrderItem() called%s
", is );
        check();
        orderItems.add( item );
    }

    @Override
    public void removeOrderItem(OrderItem item) {
      System.out.printf("removeOrderItem() called%s
", this);
      check();
      orderItems.remove( item );
    }

    @Override
    public List<OrderItem> getOrderItems() {
        check();
        return orderItems;
    }

    @Remove
    public void release() {
        System.out.printf("SCB#release() called%s
", this );
        orderItems.clear();
        customer = null;
    }
}

The ShoppingCartBean bean is annotated with @javax.ejb.Stateful, which denotes it as a stateful session bean. It also extends both business interfaces, namely: ShoppingCart and ShoppingCartLocal.

There is an initialize() method, which is the entry point for starting a shopping cart with empty content. It is the method that would be called from a native client, an EJB client or other way into this session bean. The method accepts a customer object, and stores it in the EJB.

The check() method performs a sanity check and verifies that the stateful EJB was associated with a customer.

The addOrderItem() and removeOrderItem() methods append an order item or remove an order from the collection of order items, respectively.

The method getOrderItems() returns the list collection of order items to the caller.

Finally, the method release() clears the list collection of order items, sets the internal customer of the stateful EJB to null. This method is also annotated with @javax.ejb.Remove that also makes sure the customer resource is released. If the EJB container detects that the stateful EJB is not being used anymore after a reasonable time period, say the user goes off to lunch without completing the task, the associated HTTP Session, if any, expires.

There is another way to write ShoppingCartBean that helps to simplify the POJO:

@Stateful
@Remote({ShoppingCartLocal.class}
@Local({ShoppingCartLocal.class}
public class ShoppingCartBean {  /* ... */ }

The @Remote and @Local annotations both accept class parameters for situations where the bean does not implement its business interfaces.

For completeness, let us see the definitions of the OrderItem class:

package je7hb.basic.ejb;
import java.io.Serializable;

public final class OrderItem implements Serializable {
    private final int quantity;
    private final Product product;

    public OrderItem(int quantity, Product product) {
        this.quantity = quantity;
        this.product = product;
    }

    public int getQuantity() { return quantity; }
    public Product getProduct() { return product;}
  // equals(), hashCode() and toString() ommitted
}

And also the Product class:

package je7hb.basic.ejb;
import java.io.Serializable;
import java.math.BigDecimal;

public final class Product implements Serializable {
    private final int id;
    private final String name,description;
    private final BigDecimal price;

    public Product(int id, String name,
           String description, BigDecimal price ) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.price = price;
    }

    public int getId() {return id; }
    public String getName() { return name; }
    public String getDescription() {
        return description; }
    public BigDecimal getPrice() {
        return new BigDecimal(price.doubleValue());
    }

  // equals(), hashCode() and toString() ommitted
}

As usual, let us see all of this code in action with another Arquillian integration test. Here is the code:

package je7hb.basic.ejb;
// imports omitted
@RunWith(Arquillian.class)
public class ShoppingCartBeanTest {
    @Deployment
    public static JavaArchive createDeployment() {
         /* ... */
        return jar;
    }

    Product p1 = new Product(
            1000, "IWG", "Iron Widget Grayson",
            new BigDecimal("4.99" ));
    Product p2 = new Product(
            1002, "MSB", "Miller Steel Bolt",
            new BigDecimal("8.99" ));
    Product p3 = new Product(
            1004, "ASCC", "Alphason Carbonite",
            new BigDecimal("15.99" ));
    Customer customer = new Customer("Fred","Other");

    @EJB ShoppingCartLocal cart;
    void dumpCart( List<OrderItem> items ) { /* ... */ }

    @Test
    public void shouldAddItemsToCart() {
        System.out.printf("cart = %s
", cart);
        assertNotNull(cart);

        cart.initialize(customer);

        System.out.printf("Initial state of the cart
");
        dumpCart( cart.getOrderItems());

        OrderItem item1 = new OrderItem( 4, p1 );
        cart.addOrderItem( item1 );
        assertEquals( 1, cart.getOrderItems().size() );

        System.out.printf("After adding one item
");
        dumpCart( cart.getOrderItems());

        OrderItem item2 = new OrderItem( 7, p2 );
        cart.addOrderItem( item2 );
        assertEquals(2, cart.getOrderItems().size());

        System.out.printf("After adding two items");
        dumpCart( cart.getOrderItems());

        OrderItem item3 = new OrderItem( 10, p3 );
        cart.addOrderItem( item3 );
        assertEquals(3, cart.getOrderItems().size());

        System.out.printf("After adding three items
");
        dumpCart( cart.getOrderItems());
        cart.release();
    }
}

The EJB Container injects the ShoppingCart session stateful bean into the test, or rather the Arquillian performs this task. Simply declaring the annotation @EJB in the client causes the EJB Container to inject a reference to the dependency.

The test defines a set of products and a customer as properties, and in the test method shouldAddItemsToCart() we exercise the stateful bean. First, we reset the shopping cart bean with a customer by calling the method initialize(), and proceed to add order items to the cart. We assert on each addition the correct size of the order item collection.

Finally, to simulate the finishing of the cart by the user, we call the bean's release() method. In a true application, this method would be precipitated in a checkout or reset-cart-empty-status function.

There is a method dumpCart(), which iterates over the order items and calculates the total price of the content, whilst at the same time printing the contents to the standard output.

We could have been more pedantic in the unit test and verified the sum total of the cart for each addition of an order item. This is left as an exercise to the order; more relevant would be to rewrite the test so far into proper behaviorally driven design tests.

Here is a screenshot of the test:

Stateful session beans

Singleton session beans

Java EE 6 introduced the notion of Singleton session beans into the specification. A singleton is an object that is only instantiated once per application. A purpose of a singleton usually is a global reference point that connects other dependencies. A singleton bean exists for the entire lifetime of the application. As the EJB container, which presumably lives inside a Java EE product, starts the application, singleton session beans are also started. The clients that concurrently access the instance share a singleton bean. When the EJB container removes the application, singleton enterprise bean instances are released.

A singleton session bean instance exists for the entire lifecycle of the enterprise application and there is only one instance per application. They are useful for start-up business logic. If there is a particular connection resource that needs to be shared by other enterprise java beans in the application, and usually those resources need to be the first items that exist in an application, singleton bean instances are designed for these requirements.

An EJB singleton session bean is declared with the annotation @javax.ejb.Singleton on a concrete class. The singleton may extend business interfaces or extend a super class.

Let us declare a business interface for a singleton EJB session bean. Suppose we have a business requirement for a conductor component that orchestrates the work of other components in an application. We can write a remote interface for our EJB like this:

package je7hb.basic.ejb;
import javax.ejb.Remote;

@Remote
public interface Conductor {
    public void orchestrate( String data );
}

The Conductor interface defines the remote access contract for our EJB. We already know how to write the local business interface, so we do not repeat this code here. Here is the session EJB singleton class, ConductorBean:

package je7hb.basic.ejb;
import javax.annotation.*;
import javax.ejb.Singleton;
import java.util.Properties;

@Singleton
public class ConductorBean implements Conductor {
    private final Properties properties = new Properties();

    @Override
    public void orchestrate(String data) {
       System.out.printf("ConductorBean#orchestrate( %s ) %s
",
              data, this );
    }

    @PostConstruct
    public void appStartUp() {
        properties.putAll(System.getProperties());
        System.out.printf("ConductorBean#init() %s
" +
                "java.version=%s
", this,
                properties.get("java.version") );
    }

    @PreDestroy
    public void appShutDown() {
        System.out.printf("ConductorBean#shutdown() %s
", this );
        properties.clear();
    }
}

We annotate ConductorBean with @Singleton and notice that we also added lifecycle hook methods so that we can find out exactly when this EJB component is initialized by the EJB container and also when it is about to be destroyed.

The singleton bean simply stores a properties object, which is initialized with the system properties in the appStartUp() method and cleared inside the appShutDown() method.

Here is another Arquillian integration test that demonstrates how our EJB component works:

package je7hb.basic.ejb;

// imports omitted
import javax.ejb.EJB;
import static org.junit.Assert.assertEquals;

@RunWith(Arquillian.class)
public class ConductorBeanTest {
    @Deployment
    public static JavaArchive createDeployment() {
        JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
              /* ... */;
        return jar;
    }

    @EJB Conductor conductor1;
    @EJB Conductor conductor2;
    @EJB Conductor conductor3;

    @Test
    public void shouldInjectSingletonEJB() {
        System.out.printf("conductor1 = %s
", conductor1 );
        conductor1.orchestrate("conductor1");
        System.out.printf("conductor2 = %s
", conductor2 );
        conductor1.orchestrate("conductor2");
        System.out.printf("conductor3 = %s
", conductor3 );
        conductor1.orchestrate("conductor3");
        assertEquals( conductor2, conductor1 );
        assertEquals( conductor2, conductor3 );
        assertEquals( conductor1, conductor3 );
    }
}

There are three separate references to the ConductorBean in the unit test. Because they are references to a singleton session EJB component, all of the instances should be exactly the same object, a single instance in the EJB component. The is purpose of the unit test is to validate this assertion. We expect the value of the object reference conductor1 to be equal to conductor2, which in turn is equal to conductor3, otherwise we do not have a singleton instance, but a serious problem!

This test does pass with flying colors, let's look at its screenshot:

Singleton session beans

The output clearly illustrates that the singleton instance is just that one single instance shared by EJB client references. It also shows the container calling the initialization and the predestruction methods.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.156.251