Thẻ
Today you’ll learn how to develop applications that maintain the conversational state on behalf of the client. You’ll work on a complete example of developing a stateful session bean.
The sample university registration application enables students to browse the online course catalog. While browsing the catalog, the student can select the course(s) she likes and place them in a temporary enrollment cart. The student can view or delete courses from the cart, and may later decide to place an order for the cart contents.
EnrollmentCart represents a collection of courses selected by a student in a particular session. The cart should not be shared, because it represents a particular interaction with a particular student and is alive only for the student’s session. Also, the cart is not saved into the database unless the student is ready to place an order. The cart becomes a persistent order when the student decides to purchase it. A cart must be allocated by the system for each student concurrently connected to the Web site. All these characteristics make EnrollmentCart an ideal candidate for a stateful session bean. You will undertake each of the following:
-
Examine the interactions between the client, EJB container, and the stateful session bean by looking under the hood of the bean
-
Define the home and component interfaces for the stateful session bean
-
Implement the stateful session bean class
-
Learn how to write the deployment descriptors, and package and deploy the enterprise bean
-
Write a sample client that accesses the stateful session bean
Looking Under the Hood of the Stateful Session Bean
Figure 6.1 shows the interactions between the client, the EJB container, and the stateful session bean.
Figure 6.1. Under the hood of a stateful session bean.
The following steps describe the sequence of interactions in detail:
-
At startup, the EJB container registers enterprise beans with the Java Naming and Directory Interface (JNDI) service.
-
The client looks up the home interface of the installed enterprise bean via JNDI. For example, the remote home interface for the enrollment cart session bean can be located using the following code segment:
Context initialContext = new InitialContext(); Object obj = initialContext.lookup("day06/EnrollmentCartHome"); EnrollmentCartHome eCartHome = (EnrollmentCartHome) javax.rmi.PortableRemoteObject.narrow(obj, EnrollmentCartHome.class);
-
The client uses the remote home interface to create an enrollment cart session object. For example,
EnrollmentCart eCart = (EnrollmentCart) eCartHome.create();
The container creates a session bean instance on behalf of the client. The creation process involves setting the context and calling the appropriate ejbCreate<method> method. For example, the container calls the setSessionContext and the ejbCreate methods of the session bean instance.
-
The client calls business methods on the remote object For example, the client adds courses to the enrollment cart as follows:
String[] courseIds = { "CS101", "CS102", "CS103"}; enrollmentCart.addCourses(courseIds);
The container calls the appropriate business method on the session bean. For example, it calls session bean’s method:
addCourses(String[] courseIds).
-
The client calls the remove method of the remote object. For example, the client removes the enrollment cart object as follows:
enrollmentCart.remove();
The container calls the ejbRemove method on the session bean instance.
Note
A client never directly accesses instances of the session bean’s class.
During the life cycle of the stateful session bean, the container can passivate and activate the session bean instance. This usually occurs when the number of instances reaches a certain limit specified by the developer in the deployment descriptor. During this process, the container calls the session bean’s ejbPassivate and ejbActivate methods.
Designing the Stateful Session Bean
Like other enterprise beans, stateful session beans consist of a home interface, component interface, enterprise bean class, and deployment descriptor. The home and component interfaces can be local, or remote, or both.
Figure 6.2 shows the design of the EnrollmentCart component. The EnrollmentCart stateful session bean implements the SessionBean interface. It implements the methods setSessionContext(), ejbCreate(), ejbActivate(), ejbPassivate(), and ejbRemove() as defined in the javax.ejb.SessionBean interface. In addition, it implements the addCourses(String[] courseIds) method, which accepts an array of course IDs and adds them to the cart. The getCourses() method returns a collection of course IDs in the cart and the empty() method clears the contents in the cart.
Figure 6.2. EnrollmentCart sample class diagram.
We provide remote interfaces to our stateful session bean. They include a remote home interface (EnrollmentCartHome) and a remote interface (EnrollmentCart). The EnrollmentCartHome home interface extends the javax.ejb.EJBHome interface and defines a single create() method. The EnrollmentCart remote interface extends the javax.ejb.EJBObject interface and defines the methods addCourses(), getCourses() and empty().
Note
Both stateful and stateless session beans provide the following class files: a) session bean class; b) session bean’s remote home interface and remote interface, if the session bean provides a remote client view; c) session bean’s local interface and local home interface, if the session bean provides a local client view.
Your session bean may provide both local and remote client views
Implementing the Stateful Session Bean
This section discusses the implementation of the remote home interface EnrollmentCartHome, the remote interface EnrollmentCart, and the stateful session bean class EnrollmentCartEJB.
Defining the Home Interface
Clients use home interface to create and remove session bean instances. Within the home interface, we define one or more create<method>(...) methods. The container tools generate the class that corresponds to this interface.
Note
A stateful session bean may have more than one create() methods, and some of them may have arguments. This is different, though, from the case of a stateless session bean, which can only have one create() method with no arguments.
Listing 6.1 shows the home interface EnrollmentCartHome.
Listing 6.1 The Full Text of day06/EnrollmentCartHome.java
package day06; import java.rmi.RemoteException; import javax.ejb.*; public interface EnrollmentCartHome extends EJBHome { EnrollmentCart create() throws CreateException, RemoteException; }
The EnrollmentCartHome interface consists of a single create() method. To create the bean instance, a client calls the create() method of the home interface.
The throws clause of the create() method must include CreateException. This exception is thrown when there is a problem in creating or initializing the bean instance.
The remote home interface EnrollmentCartHome is a Java Remote Invocation Method (RMI) interface. So, the method arguments and return types of a remote method must be legal types for the RMI over Internet Inter-ORB Protocol (RMI/IIOP), such as primitives, serializable objects, and RMI/IIOP remote objects. Each method declared in the remote interface must include java.rmi.RemoteException in its throws clause. This exception is thrown when a remote invocation fails for some reason, such as network failure, protocol errors, and so on.
Defining the Remote Interface
This interface exposes a session bean’s business methods to the client. The client calls the methods defined in the remote interface to invoke the business logic implemented by the bean. The container tools generate the class corresponding to this interface.
As shown in Listing 6.2, our EnrollmentCart interface defines three business methods: addCourses() for adding courses to the cart, getCourses() to get the currently selected courses, and empty() to clear the enrollment cart.
Listing 6.2 The Full Text of day06/EnrollmentCart.java
package day06; import javax.ejb.*; import java.rmi.RemoteException; import java.util.Collection; public interface EnrollmentCart extends EJBObject { public void addCourses(String[] courseIds) throws RemoteException; public Collection getCourses() throws RemoteException; public void empty() throws RemoteException; }
The remote interface EnrollmentCart is a Java RMI interface. So, method arguments and return types of a remote method must be legal types for RMI/IIOP and the method must include java.rmi.RemoteException in its throws clause.
Implementing the Enterprise Bean Class
Listing 6.3 shows the EnrollmentCartEJB enterprise bean class.
Listing 6.3 The Full Text of day06/EnrollmentCartEJB.java
package day06; import java.util.*; import javax.ejb.*; import javax.naming.*; public class EnrollmentCartEJB implements SessionBean { private SessionContext ctx; private HashSet cart; public EnrollmentCartEJB() { print("The container created this instance.\n"); } public void setSessionContext(SessionContext ctx) { print("The container called the setSessionContext method "); print("to associate session bean instance with its context.\n"); this.ctx = ctx; } public void ejbCreate() throws CreateException { print("The container called the ejbCreate method.\n"); cart = new HashSet(); } public void ejbActivate() { print("This instance has just been reactivated.\n"); } public void ejbPassivate() { print("The container intends to passivate the instance.\n"); } public void ejbRemove() { print("This instance is in the process of being removed "); print("by the container.\n"); } public void addCourses(String[] courseIds) { print("The container called addCourses method.\n"); if ( courseIds == null) { return; } for ( int i = 0; i < courseIds.length ; i ++ ) { cart.add(courseIds[i]); } } public Collection getCourses() { print("The container called getCourses method.\n"); return cart; } public void empty() { print("The container called empty method.\n"); cart.clear(); } void print(String s) { System.out.println(s); } }
The stateful session bean implements the javax.ejb.SessionBean interface. The member variable cart constitutes the enterprise bean’s conversational state. The bean implements the methods setSessionContext(), ejbCreate(), ejbActivate(), ejbPassivate(), and ejbRemove(), as defined in the javax.ejb.SessionBean interface. The ejbCreate() method initializes the bean instance. The bean class implements the business methods addCourses(), getCourses() and empty() defined in its remote interface.
Note
The EJB container serializes calls to each stateful session bean instance. This enables you to program a stateful bean as single-threaded, non-reentrant code. If two clients attempt to simultaneously access a session bean instance, the container throws an exception to the second client. The container throws java.rmi.RemoteException if the client is a remote client and javax.ejb.EJBException if the client is a local client.
The container does not serialize calls to a stateless session bean instance because the container routes each client request to a different instance of the session bean class.
Optional SessionSynchronization Interface
The SessionSynchronization interface, when implemented by the stateful session bean, allows the bean to be more transaction-aware. The container provides the bean with three callback methods: afterBegin(), beforeCompletion(), and afterCompletion(int). When the transaction begins, the container calls the afterBegin() method. The beforeCompletion() and afterCompletion(int) methods are used to manage resources before the transaction commits and aborts. We’ll explore this interface on Day 16.
Declaring the Deployment Descriptors
Now it’s time to create the deployment descriptors. We need to create two deployment descriptors before creating the bean’s JAR file (in our case, day06_EnrollmentCart.jar), which is required to deploy the enrollment cart’s stateful session bean into the WebLogic EJB container. These deployment descriptors are
-
Standard deployment descriptor ejb-jar.xml, as specified by Sun Microsystems, which is common to all EJBs
-
Vendor-specific deployment descriptor; WebLogic uses weblogic-ejb-jar.xml and JBoss uses jboss.xml
Declaring the Standard Deployment Descriptor ejb-jar.xml
This file is the standard descriptor as specified by Sun, and must contain the Sun Microsystems–specific EJB DTD.
The ejb-jar.xml describes the enterprise bean’s deployment properties, such as its type and structure. As we learned on Day 2, this file provides the EJB container with information where it can find, and then load, the home interface, remote interface, and bean class. It declares its internal dependences and the application assembly information, which describes how the enterprise bean in the ejb-jar file (day06_EnrollmentCart.jar) is assembled into an application deployment unit.
Listing 6.4 shows the ejb-jar.xml file for the deployment descriptor of the EnrollmentCart EJB.
Listing 6.4 The Full Text of day06/ejb-jar.xml
<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <ejb-jar> <enterprise-beans> <session> <ejb-name>EnrollmentCart</ejb-name> <home>day06.EnrollmentCartHome</home> <remote>day06.EnrollmentCart</remote> <ejb-class>day06.EnrollmentCartEJB</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar>
The prolog contains the declaration and the DTD for the validation. The document root is the <ejb-jar> tag. It contains the structural information about all the included enterprise beans. The enterprise-beans element contains the declarations of one or more enterprise beans. The session element declares a session bean, and an ejb-name element within the session element defines the session bean’s name (EnrollmentCart). The session element also declares other things such as the home interface (day06.EnrollmentCartHome), the remote interface (day06.EnrollmentCart), and the bean’s class (day06.EnrollmentCartEJB). The session-type element declares that this is a stateful session bean.
The transaction-type element specifies that this bean uses container-managed transactions. Container-managed transactions are discussed in detail on Day 18.
Tip
The deployment descriptor element session-type is where the EJB container recognizes a stateful or stateless session bean.
Declaring the Vendor-Specific Deployment Descriptor
As you learned on Day 2, in addition to standard ejb-jar.xml, an application typically requires a certain amount of additional environment-specific or vendor-specific binding information, such as naming, security, and persistence. The following sections describe both the WebLogic and JBoss deployment descriptors.
Declaring the WebLogic Deployment Descriptor: weblogic-ejb-jar.xml
The weblogic-ejb-jar.xml file contains the WebLogic Server–specific EJB DTD that defines the naming, caching, clustering, and performance behavior of the deployed EJBs.
Listing 6.5 shows the weblogic-ejb-jar.xml deployment descriptor.
Listing 6.5 The Full Text of day06/weblogic-ejb-jar.xml
<?xml version="1.0"?> <!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN' 'http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd'> <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>EnrollmentCart</ejb-name> <stateful-session-descriptor> <stateful-session-cache> <max-beans-in-cache>5</max-beans-in-cache> </stateful-session-cache> </stateful-session-descriptor> <jndi-name>day06/EnrollmentCartHome</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
The jndi-name element declares day06/EnrollmentCart as the JNDI name of the EnrollmentCart enterprise bean. The max-bean-in-cache element specifies the threshold at which WebLogic Server starts to passivate inactive instances in the instance cache to the back store. For demonstration purposes, we set the value to 5. As new concurrent clients request the bean’s services, WebLogic Server creates new instances of the bean. When the sixth client requests the bean’s services, the server passivates some of the idle beans, perhaps using the LRU (Least Recently Used) algorithm.
Declaring the JBoss Deployment Descriptor: jboss.xml
jboss.xml, the deployment descriptor that’s specific to the JBoss server, defines the naming, caching, and other properties to control the behavior of the deployed EJBs. Listing 6.6 shows the jboss.xml file.
Listing 6.6 The Full Text of day06/jboss.xml
<?xml version="1.0" encoding="UTF-8"?> <jboss> <enterprise-beans> <session> <ejb-name>EnrollmentCart</ejb-name> <jndi-name>day06/EnrollmentCartHome</jndi-name> </session> </enterprise-beans> <container-configurations> <container-configuration> <container-name>Standard Stateful SessionBean</container-name> <container-cache-conf> <cache-policy> <![CDATA[org.jboss.ejb.plugins.LRUStatefulContextCachePolicy]]> </cache-policy> <cache-policy-conf> <max-capacity>100</max-capacity> <max-bean-age>2</max-bean-age> <overager-period>2</overager-period> </cache-policy-conf> </container-cache-conf> </container-configuration> </container-configurations> </jboss>
In this deployment descriptor, the jndi-name element declares day06/EnrollmentCart as the JNDI name of the EnrollmentCart enterprise bean. The max-capacity element specifies the maximum capacity of the cache as 100. The element max-bean-age element specifies the maximum period of inactivity in seconds that a bean can have before it will be passivated by the container. The overager-period element specifies the period in seconds at which the container scans the cache for inactive beans and passivates them.
For demonstration purposes, we specify the container look for inactive beans every two seconds and the bean is considered inactive if it is not accessed for two seconds.
Writing the Client
Listing 6.7 demonstrates how a client accesses a stateful bean.
Listing 6.7 The Full Text of day06/Client.java
package day06; import java.util.*; import javax.naming.*; import javax.ejb.*; public class Client { public static void main(String[] args) { print("Starting Client . . .\n"); Context initialContext = null; EnrollmentCartHome enrollmentCartHome = null; EnrollmentCart enrollmentCart = null; print("Demonstration of a simple client . . . \n"); try { // Looking up the enrollment cart home via JNDI initialContext = new InitialContext(); Object object = initialContext.lookup("day06/EnrollmentCartHome"); enrollmentCartHome = (EnrollmentCartHome) javax.rmi.PortableRemoteObject.narrow(object, EnrollmentCartHome.class); print("Creating an enrollment cart.\n"); enrollmentCart = (EnrollmentCart) enrollmentCartHome.create(); String[] courseIds = { "CS101", "CS102", "CS103"}; print("Adding some courses to our enrollment cart.\n"); enrollmentCart.addCourses(courseIds); String[] moreCourseIds = { "CS201", "CS202", "CS203"}; print("Adding some more courses to our enrollment cart.\n"); enrollmentCart.addCourses(moreCourseIds); print("Getting the collection of courses in our enrollment cart.\n"); Collection collection = enrollmentCart.getCourses(); print("Removing our enrollment cart.\n"); enrollmentCart.remove(); } catch ( Exception e) { e.printStackTrace(); } print("Demonstration of exceptions . . .\n"); try { print("Now trying to access enrollment cart that was removed.\n"); String[] courseIds = { "CS501" }; enrollmentCart.addCourses(courseIds); } catch ( Exception e) { print("Exception caught while trying to access "); print(" enrollment cart that was removed.\n"); print(e.toString()); } print("Demonstration of Activation/Passivation . . .\n"); try { EnrollmentCart carts[] = new EnrollmentCart[15]; for ( int i = 0; i < 15 ; i++ ) { print("Creating cart " + i ); carts[i] = enrollmentCartHome.create(); String[] courseIds = { "CS601" }; carts[i].addCourses(courseIds); Thread.sleep(1000); } for(int i = 0; i < 10; i++ ) { print("Removing cart" + i ); carts[i].remove(); } } catch(Exception e) { e.printStackTrace(); } } static void print(String s) { System.out.println(s); } }
The client locates the EnrollmentCartHome home interface of the deployed enterprise bean via JNDI, and then uses the remote home interface to create a remote EnrollmentCart session object. The client then calls the addCourses() business method, followed by the getCourses() business method, and removes the cart by calling remove method on the remote interface. Later, the client tries to access the session object that was removed earlier. This results in a java.rmi.NoSuchObjectException exception.
The client also demonstrates the activation and passivation by creating multiple instances of the bean. As you know, the container starts to passivate the instances when the number of instances in the cache reaches the threshold set in the vendor-specific deployment descriptor.
Caution
The EJB container may remove the session object in the following scenarios: a) A timeout due to client inactivity while the instance is in the passive state; b) A shutdown or crash of the container; c) A system exception thrown from the instance’s method. All the object references and handles for the session object become invalid. If your client attempts to access the session object, the container will throw a java.rmi.NoSuchObjectException exception if the client is a remote client, or the javax.ejb.NoSuchObjectLocalException exception if the client is a local client
-
-
Packaging and Deploying the Enterprise Bean
The following shows the directory structure for the EnrollmentCart bean files and client for WebLogic Server:
C:\styejb\ examples\ day06\ EnrollmentCart.java EnrollmentCartHome.java EnrollmentCartEJB.java ejb-jar.xml weblogic-ejb-jar.xml Client.java
For JBoss, replace the vendor-specific weblogic-ejb-jar.xml file with jboss.xml.
For JBoss, replace the vendor-specific weblogic-ejb-jar.xml file with jboss.xml.
To package and deploy the EnrollmentCart session bean for WebLogic Server, run the following commands:
C:>cd styejb\examples C:\styejb\examples>setEnvWebLogic.bat C:\styejb\examples>cd day06 C:\styejb\examples\day06>buildWebLogic.bat
You can run the script by entering the following commands:
C:>cd styejb\examples C:\styejb\examples>setEnvWebLogic.bat C:\styejb\examples>cd day06 C:\styejb\examples\day06>buildWebLogic.bat
The corresponding script for JBoss is buildJBoss.bat. Here are the steps to run the commands:
C:>cd styejb\examples C:\styejb\examples>setEnvJBoss.bat C:\styejb\examples>cd day06 C:\styejb\examples\day06>buildJBoss.bat
To deploy the EnrollmentCart bean, we used the hot deployment feature of both WebLogic (version 6.1 and higher) and JBoss (version 2.4 and higher). Deployment is performed simply by copying the bean’s JAR file into the application deployment directory
-
Running the Example
The following steps describe how to run the example in either WebLogic Server or JBoss:
The following steps describe how to run the example in either WebLogic Server or JBoss:
-
Start the application server in a command window.
In the case of WebLogic Server, use the following steps:
C:>cd styejb\examples C:\styejb\examples>setEnvWebLogic.bat C:\styejb\examples>startWebLogic.bat
In the case of JBoss, use the following steps:
C:>cd styejb\examples C:\styejb\examples>setEnvJBoss.bat C:\styejb\examples>startJBoss.bat
-
Start the client program in another command window.
In the case of WebLogic Server, use the following steps:
C:>cd styejb\examples C:\styejb\examples>setEnvWebLogic.bat C:\styejb\examples>cd day06 C:\styejb\examples\day06>runClientWebLogic.bat
In the case of JBoss, use the following steps:
C:>cd styejb\examples C:\styejb\examples>setEnvWebLogic.bat C:\styejb\examples>cd day06 C:\styejb\examples\day06>runClientJBoss.bat
The output of the EnrollmentCart, on both the client window, and the server window, should look like the following (see Figure 6.3). The correlation between both outputs is depicted in the figure.
Figure 6.3. Sample output from running the example.
-
-
Best Practices
A client should explicitly remove a stateful session bean by calling the remove() method of the component interface. Otherwise, the container keeps the stateful bean until it times out. This wastes resources such as memory, secondary storage, and so on.
Stateful session beans cannot be shared among different clients. A multithreaded client (such as a servlet or a Swing application) must serialize all its calls to the stateful session bean. Simultaneous access to a stateful session bean results in a java.rmi.RemoteException exception.
Consider tuning the stateful session bean’s instance cache size. For best performance, the maximum number of beans in the cache should be equal to maximum number of concurrent clients. If the cache size is less than the number of concurrent clients, the container triggers activation and passivation, which degrades performance.
-
Summary
Today you learned how to implement and deploy a stateful session bean. The home interface extends javax.ejb.EJBHome interface and contains create method(s). The remote interface extends javax.ejb.EJBObject and contains the business methods callable by the client. The enterprise bean class implements the javax.ejb.SessionBean interface. In addition, it implements the ejbCreate method(s) corresponding to the create methods defined in the home interface and business methods defined in the remote interface. The container recognizes the session bean type from the bean’s deployment descriptor.
The EJB container serializes all calls to the stateful session bean instance and throws exception if clients attempt to simultaneously access the same bean instance. Also, the container throws an exception if the client attempts to access the session object that was removed earlier