Thursday, January 18, 2007

Session beans

Session beans


EJB Tutorials


Session beans overview


Session beans are reusable components that have conversations or sessions with clients. In many ways, session beans are the workhorses of the EJB component family, at various times providing direct functionality, managing or controlling interaction with resources and other beans, and acting as a facade or liaison for cooperating beans. Their lifetimes and life cycles are relatively short and dependent on the client. Session beans are non-persistent and live no longer than the client and possibly less. Under the hood, the container may actually reuse session beans, but from the client's perspective, once they are gone, they are permanently gone. There are two types of session beans, stateless and stateful, and they have distinctly different types of relationships with their clients. The container is made aware of the type of the session bean through a deployment descriptor entry.

Figure 3. The javax.ejb.SessionBean interface
The javax.ejb.SessionBean interface

When you write any session bean, you must implement the javax.ejb.SessionBean interface shown in Figure 3. All beans have an associated context; for session beans, the container calls setSessionContext() after instance creation, which allows the bean to obtain a reference to the session context. The ejbActivate() and ejbPassivate() methods are only invoked on stateful session beans. ejbRemove() is called at the end of a session bean's lifetime for any cleanup before destruction.



Back to top


Stateless session beans

Stateless session beans do not maintain conversational state with any given client for longer than the duration of a specific method call. From the client perspective, then, a stateless session bean may be seen as a group of methods that use only local variables, remembering nothing from previous client interaction. In addition, the container may choose to serve the same instance of a stateless session bean to multiple clients or use different bean instances at different times to service one client's requests: the container sees every instance of a specific session bean type as equivalent. One implication of the way stateless session beans work is that there can only be a single ejbCreate() method, with no arguments. A container may choose to passivate (put to secondary storage) a stateful session bean, but never does so with stateless session beans. Because of these characteristics, stateless session beans can offer greater scalability and performance than most other types of beans.

There are only two milestones in a stateless session bean's life cycle: Does Not Exist and Ready. A container may maintain a method-ready pool, which is a pool of active, ready-to-serve beans. To place a session bean in the Ready state, the container:

  • Instantiates a bean using the Class.newInstance() method
  • Invokes the bean's setSessionContext() method
  • Invokes the bean's single ejbCreate() method

When the container no longer requires the bean, it calls the bean's ejbRemove() method. After this action, the bean is effectively destroyed and back to the Does Not Exist state.

Note that there is not necessarily any correlation in time between a client's create() and remove() calls and a container's ejbCreate() and ejbRemove() calls. Also, the bean class must have a public, no-arg constructor to accommodate Class.newInstance() invocations.

It is important to understand that stateless session beans can maintain state (that is, use instance variables) among internal method invocations, for non-client-related data, or for data and references that apply to all clients as opposed to a specific client. If the client invokes bean method A, for example, and A then calls the bean's B method, which calls the bean's C method, the state from method A through method C can be maintained. Once method A returns, however, that state is no longer reliable for the given client.




Example: A metric conversion program

Our first example uses a stateless session bean and a Web component to provide an application that performs various conversions between U.S. English and metric measures. It might come in handy for a salesman working for a U.S. company in Europe or similar scenarios. For a look at the display and user view of the application, see The Metric Converter application. You should also read about and deploy the Alice's World page in order for everything to work properly; details are at Appendix C: About the example applications.

Stateless session beans do not have much code to interact with the container. In this example, you will see that the SessionBean implementation is all empty methods. As to the functional part of MetricCvtBean, there is a convert() method that takes a String to identify the type of conversion desired, and a double, which is the value to convert. All of the other methods convert to or from the various units. The most important thing about the code, as far as EJB components are concerned, is that the class implements the SessionBean interface. Here's an abbreviated look at the MetricCvtBean code:

import javax.ejb.*;

public class MetricCvtBean implements SessionBean,
MetricCvtConstants
{
public MetricCvtBean() {} // end constructor

// required by the specification contract
public void ejbCreate() {} // end ejbCreate

// required for SessionBean implementation
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}

public String Convert( String sType, double dToConvert )
{
...
} // end Convert

// other methods
...

} // end class MetricCvtBean

Now that we have the main bean code, it's time to create the home and component interfaces. The Metric Converter Web component is written to remote interfaces. Here's the abbreviated remote component interface code:

import java.rmi.RemoteException;
import javax.ejb.*;


public interface MetricCvtRemote extends EJBObject,
MetricCvtConstants
{
public String Convert( String sType, double dToConvert )
throws RemoteException;

// other method declarations
...

} // end MetricCvtRemote

The important features of the remote component interface are that it extends EJBObject and that every method is declared to throw a RemoteException. In contrast, a local component interface extends EJBLocalObject and does not throw RemoteExceptions; the code looks very much like a standard Java interface.

Next, we'll create the remote home interface:

import javax.ejb.*;
import java.rmi.RemoteException;

public interface MetricCvtRemoteHome extends EJBHome
{
// required
MetricCvtRemote create() throws RemoteException,
CreateException;
} // end MetricCvtRemoteHome

The important things here are that the interface extends EJBHome, that the create() method returns the remote component interface, and that the method throws RemoteException and CreateException. A local home interface would extend EJBLocalHome, its create() method would return the local component interface, and the method would throw only CreateException.

Now it's time to compile everything. You'll notice that the J2EE documentation recommends using Ant, and you will be well rewarded for learning how to use the tool; but this tutorial can only cover so much, so we will rely on the familiar and standard javac command. Go to the MetricCvt directory, and from the command line enter javac *.java to compile all .java files in the directory. If you prefer, you can copy the .class files from the MetricCvt/prod directory.

Next, we want to take a quick look at the client code (the client is a JSP page) to access and use the bean. Following are the relevant portions:

  private MetricCvtRemote mc = null;

public void jspInit()
{
InitialContext ic;
MetricCvtRemoteHome mcHome;
Object oRef;
String sMsg = "Couldn't create MetricCvtBean.";

try
{
ic = new InitialContext();
oRef =
ic.lookup( "java:comp/env/ejb/MetricCvtBean" );
mcHome =
(MetricCvtRemoteHome)PortableRemoteObject.
narrow( oRef, MetricCvtRemoteHome.class );
mc = mcHome.create();
}
catch( RemoteException re )
{
System.out.println( sMsg +
re.getMessage() );
}
catch( CreateException ce )
{
System.out.println( sMsg +
ce.getMessage() );
}
catch( NamingException ne )
{
System.out.println( "Unable to lookup home: " +
"MetricCvtBean. " + ne.getMessage() );
}
} // end jspInit


public void jspDestroy()
{
try
{
mc.remove();
}
catch( RemoteException remoteEx )
{
// don't care
}
catch( RemoveException removeEx )
{
// don't care
}

mc = null;
} // end jspDestroy

In jspInit(), the code creates an InitialContext and then does a JNDI lookup for MetricCvtBean. It then retrieves the home interface and uses the create() method to get a reference to the component interface. In jspDestroy(), the bean is removed and the reference is set to null.

A client uses the retrieved component interface to invoke exposed bean methods. The actual use of the bean in the Metric Converter is to display its output from the Convert() method, as shown by the following statement:

 <%= mc.Convert( sRB, d ) %>




Deploying the Metric Converter example

If you deployed the Alice's World page (see Appendix C: About the example applications ), you have an idea of the steps involved in deployment. If you haven't, you should do so now, because the Metric Converter application expects to return to that page. We are going to go through the deployment process step by step for both the Enterprise JavaBean component and the Web component here. For the other examples, I recommend that you copy the .ear file from the associated prod directory, open it in deploytool, and deploy from that (in other words, start at the step labeled "Deployment" below), unless you want more deployment practice. Another good way to practice is to copy an application directory to one of your own, then create the application from scratch with a new name, using the original .ear file for reference.

The MetricCvtBean deployment:

  1. Start J2EE and deploytool.
  2. Create new application:
    • From the menu, select File, New, Application.
    • Browse to the MetricCvt folder.
    • Key MetricCvt.ear in for the file name.
    • Click New Application.
    • Application Display name is MetricCvt by default.
    • Click OK.
  3. Create new Enterprise JavaBean component:
    • Ensure that Application MetricCvt is selected.
    • From the menu, select File, New, Enterprise Bean -- Intro display appears, click Next.
    • Select Create New JAR File In Application.
    • Ensure that MetricCvt is selected in the drop-down menu.
    • In JAR Display Name, key MetricCvtJAR.
    • Click Edit.
    • Select the .class files that comprise the bean. These are:
      • MetricCvtBean.class
      • MetricCvtConstants.class
      • MetricCvtRemote.class
      • MetricCvtRemoteHome.class
    • Click Add; click OK; click Next.
    • Under Bean Type, click Session and Stateless.
    • In the Enterprise Bean Class combo box, select the bean implementation: MetricCvtBean.
    • In Enterprise Bean Name, accept MetricCvtBean.
    • Select the corresponding interfaces in the combo boxes:
      • For Remote Home Interface, select: MetricCvtRemoteHome.
      • For Remote Interface, select: MetricCvtRemote.
    • Click Next. You may continue with Next to look at the additional displays until you get to Finish, or click Finish now. For this bean, there are no more entries.
    • MetricCvtJAR and MetricCvtBean now appear under MetricCvt.
    • Select MetricCvtJAR, then click the JNDI Names tab.
    • Under EJBs, key ejb/MetricCvtBean next to MetricCvtBean.
  4. Deployment. All of the preceding has been set up for the deployment descriptors. We can now actually deploy the bean:
    • Select MetricCvt.
    • Select Tools Deploy from the menu.
    • Under Object To Deploy, ensure that MetricCvt is selected.
    • Under Target Server, select localhost.
    • Select the Return Client JAR check box. While our Web component won't need it, any remote clients would use this JAR for the necessary RMI/IIOP stubs.
    • Ensure that the complete path shows for MetricCvtClient.jar in the Client JAR File Name field.
    • Select Save object before deploying.
    • Click Next.
    • Verify that the JNDI name is ejb/MetricCvtBean.
    • Click Next, then Finish.

The Deployment Progress dialog will display. Wait until the progress bars are complete and the Cancel button changes to OK, then click OK. The MetricCvt Enterprise JavaBean component is deployed.

The Metric Converter Web component deployment:

  1. Start J2EE and deploytool.
  2. Create new application:
    • From the menu, select File, New, Application.
    • Browse to the MetricCvt folder.
    • Key MetricCvtApp.ear in file name.
    • Click New Application.
    • Application Display name is MetricCvtApp by default.
    • Click OK.
  3. Create new Web component:
    • Ensure that Application MetricCvtApp is selected.
    • From the menu, select File, New, Web Component -- Intro display appears, click Next.
    • Select Create New WAR File In Application.
    • Ensure that MetricCvtApp is selected in the drop-down menu.
    • In WAR Display Name, key MetricCvtAppWAR.
    • Click Edit.
    • Select the .jsp file and the bean interface .class files needed for the JSP files compilation. These are:
      • index.jsp
      • MetricCvtConstants.class
      • MetricCvtRemote.class
      • MetricCvtRemoteHome.class
    • Click Add, Click OK, Click Next.
    • Select JSP for the type of Web component; click Next.
    • In the JSP filename combo box, select index.jsp.
    • In Web Component Name, allow the default -- index.
    • Click Next. There are no Initialization Parameters. Click Next.
    • Under Component Aliases, enter "/index" (without quotes).
    • You may continue with Next to look at the additional displays until you get to Finish, or click Finish now.
    • MetricCvtAppWAR now appears under MetricCvtApp.
    • Select MetricCvtAppWAR, then select the EJB Refs tab and click Add.
    • When it invokes the lookup method, the Web client refers to the home of an enterprise bean like this:
      Object objRef = ic.lookup("java:comp/env/ejb/MetricCvtBean");
      So, in the Coded Name column, enter ejb/MetricCvtBean.
    • Accept Session for Type and Remote for Interface.
    • For Home Interface, enter MetricCvtRemoteHome.
    • In the Local/Remote Interface column, enter MetricCvtRemote.
    • Now select MetricCvtApp and click on the JNDI Names tab.
    • Under References, key ejb/MetricCvtBean for the JNDI name next to the ejb/MetricCvtBean Reference Name. This maps the actual JNDI name for the bean to the name used in the application code.
  4. Deployment. We can now actually deploy the Web component:
    • Select MetricCvtApp.
    • Select Tools, Deploy from the menu.
    • Under Object To Deploy, ensure that MetricCvtApp is selected.
    • Under Target Server, we are using localhost. Be sure that is selected.
    • Unselect the Return Client JAR check box. In this case, the JSP page is the client.
    • Select Save object before deploying.
    • Click Next.
    • Verify that the JNDI name under References is ejb/MetricCvtBean; click Next.
    • Key "/MetricCvtApp" in the context root.
    • Click Next, then Finish.

The Deployment Progress dialog will display. Wait until the progress bars are complete and the Cancel button changes to OK, then click OK. The MetricCvtApp Web component is deployed.

If you look in the MetricCvt folder, you will see that it now contains MetricCvt.ear, MetricCvtApp.ear, and MetricCvtClient.jar. To run MetricCvtApp, start your browser and enter http://localhost:8000/Alice as the target URL. When the Alice's World home page appears, click on the "Alice's Metric Converter" link. The JSP page must be compiled, so the first run will appear to take a while to load. You can also run the application by entering the URL http://localhost:8000/MetricCvtApp, but the Done button expects to return to the home page, so you should start from there.




Stateful session beans

Stateful session beans are assigned to a specific client and are able to retain session state across multiple requests from that client for the duration of a bean's lifetime. Because of the link to the client and the need to retain state, stateful session beans are not as scalable as the stateless variety, although the container can still use pooling. You should consider the importance to your application of maintaining state externally, as opposed to maintaining state in the client, when deciding whether to use stateless or stateful session beans.

Other than the required life cycle methods, stateful session beans will look very similar to standard Java objects with instance variables. A public, no-arg constructor is required. There are three milestones in a stateful session bean's life cycle: Does Not Exist, Ready, and Passive. To place a session bean in the Ready state, the container:

  • Instantiates a bean using the Class.newInstance() method
  • Invokes the bean's setSessionContext() method
  • Invokes the bean's appropriate ejbCreate() method

There may be multiple ejbCreate() methods with differing input arguments, just as an ordinary Java class might have multiple constructors.

Prior to placing a bean in the Passive state, the container invokes ejbPassivate(). Before placing the bean back in the Ready state, the container calls ejbActivate().

When the container no longer requires the bean, it calls the bean's ejbRemove() method. After this operation, the bean is effectively destroyed and back to the Does Not Exist state.

A container may use passivation to enhance scalability. While stateless session beans are relatively easy to pool, stateful session beans must appear to be linked to a single client and maintain state with that client. One way to accomplish a semblance of pooling is to use something similar to context switching in an operating system's application management environment. Serialization/deserialization is most commonly employed to save/restore bean state to and from secondary storage. When the container decides the time is appropriate, it will invoke ejbPassivate() just before serialization and ejbActivate() just after deserialization. The bean should be prepared to release or reacquire, respectively, resources that are not suitable for serialization (such as a database connection) when these methods are called. If there are no serialization, performance, or efficiency implications, the bean may choose to do nothing at these times. Clearly, any acquired resources should also be released on an ejbRemove() invocation.




Example: The Rock Survey, take 1

At this point, you should take a moment to look over The Rock Survey application, because we will revisit it several times in the tutorial. As you can see, the application requires that data be maintained across four Web pages, so a stateful session bean is the right choice to manage and maintain state and data. In this first go-around, the data is not actually persisted anywhere after the client completes the survey; we'll handle that in later iterations. In anticipation, the persist() method exists, but for the moment it does nothing more than return true.

The RockSurveyBean maintains and validates Rock Survey data, and returns a data object that also contains error information for the client's use. As with the stateless session bean example, RockSurveyBean implements the SessionBean interface. Let's take a look:

import javax.ejb.*;

public class RockSurveyBean implements SessionBean
{
...

public RockSurveyBean() {} // end constructor


// required by the specification contract
public void ejbCreate() {} // end ejbCreate

// required for SessionBean implementation
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}

...

} // end class RockSurveyBean

For the Rock Survey application, only remote interfaces are defined. The extension of the remote home interface is very basic, with only one create() method:

import javax.ejb.*;
import java.rmi.RemoteException;

public interface RockSurveyRemoteHome extends EJBHome
{
// required
RockSurveyRemote create()
throws RemoteException,
CreateException;

} // end RockSurveyRemoteHome

The create() and remove() methods, except for the name of the bean (and the component interface returned from create() ), are identical to those for the client in Example: A metric conversion program. There are two things to note in the application.

First, in Example: A metric conversion program, the stateless session bean was created in jspInit() and destroyed in jspDestroy(), each of which occurs exactly once in the life of a servlet. That's because the application made use of the same bean (from its perspective) for the life of the application. Here, a stateful session bean is allocated to, and tracks state for, the duration of the entire conversation for each individual client, by definition. For that reason, on the first page the code checks if the servlet session object was newly created, and, if so, creates a new RockSurveyBean. The bean is destroyed when the session is invalidated at the end of the survey.

Secondly, the Rock Survey application spans several pages, so there must be a means of retaining the reference to the RockSurveyBean as the user moves from page to page. To resolve this issue, the bean reference is stored in and retrieved from the servlet's session object.

Next up is the remote component interface:

import java.rmi.RemoteException;
import javax.ejb.*;

public interface RockSurveyRemote extends EJBObject
{
public RockSurveyData getData()
throws RemoteException;

public boolean persist()
throws RemoteException;

public RockSurveyData setUserData(
String sGenderIn, String sLastNameIn,
String sMaritalIn, String sPostalIn )
throws RemoteException;

public RockSurveyData setRockData(
String sCountIn, String sLocationIn,
String sMeasureIn, String sPreferenceIn,
String sWeightIn )
throws RemoteException;

} // end RockSurveyRemote

Although RockSurveyBean has several methods, the component interface only exposes those the client needs to get, set, and persist the data. RockSurveyData is a helper class that effectively contains a read-only copy of the data, with a complete set of getXXX() methods, and any errors. Because the data is gathered over several pages, there are two setXXX() methods, one for general user information, and the other for the actual survey data. The RockSurveyData class always returns all of the currently available data.

You may wonder why I went to the trouble of creating the RockSurveyData class when all of the information is available from RockSurveyBean. The answer has to do with a topic you will hear discussed frequently in EJB developer circles: fine-grained versus coarse-grained access. The classic example of fine-grained access is a data object representing a row with, say, 50 columns and having 50 getters and 50 setters. A coarse-grained version might return the entire row or multiple rows from one method.

Because the Rock Survey uses remote interfaces, RockSurveyBean is a remote component, which means each method invocation is a distributed call, as discussed earlier. Fine-grained access with distributed calls is a performance killer. Since Web page data retrieval is inherently page oriented, RockSurveyBean 's gets and sets are oriented toward a page of data. The RockSurveyData class is the vehicle to carry the data in a coarse-grained manner. Once the application has a RockSurveyData instance, it can call getXXX() as usual, but against a local object, avoiding performance hits as much as possible.




Deploying the Rock Survey example

Deployment for the Rock Survey application will look very similar to Deploying the Metric Converter example. The major differences are the names, the fact that we are using a stateful session bean, and the fact that the bean and the Web component pieces are in the same EAR. Because we will deploy several versions of this application using components with the same name, we will use Survey1 for the Application EAR, WAR, and JAR files. Here are the directions:

The Survey1 application deployment:

  1. Compile Java source files in the Survey1 directory, or copy the .class files from the prod folder.
  2. Start J2EE and deploytool.
  3. Create new application:
    • From the menu, select File, New, Application.
    • Browse to the Survey1 folder.
    • Key Survey1App.ear in for the file name.
    • Click New Application.
    • Application Display name is Survey1App by default; click OK.
  4. Create new Enterprise JavaBean component:
    • Ensure that Application Survey1 is selected.
    • From the menu, select File, New, Enterprise Bean -- Intro display appears; click Next.
    • Select Create New JAR File In Application.
    • Ensure that Survey1 is selected in the drop-down menu.
    • In JAR Display Name, key Survey1JAR.
    • Click Edit.
    • Select the .class files that comprise the bean. These are:
      • RockSurveyBean.class
      • RockSurveyConstants.class
      • RockSurveyData.class
      • RockSurveyRemote.class
      • RockSurveyRemoteHome.class
      • SurveyConstants.class
    • Click Add; click OK; click Next.
    • Under Bean Type, click Session and Stateful.
    • In the Enterprise Bean Class combo box, select the bean implementation: RockSurveyBean.
    • In Enterprise Bean Name, key RockSurvey1Bean.
    • Select the corresponding interfaces in the combo boxes:
      • For Remote Home Interface, select: RockSurveyRemoteHome.
      • For Remote Interface, select: RockSurveyRemote.
    • Click Next, click Finish.
    • Survey1JAR and RockSurvey1Bean now appear under Survey1App.
    • Select Survey1JAR, then click the JNDI Names tab.
    • Under EJBs, key ejb/RockSurvey1Bean next to RockSurvey1Bean.
  5. Create new Web component:
    • Ensure that Application Survey1App is selected.
    • From the menu, select File, New, Web Component -- Intro display appears; click Next.
    • Select Create New WAR File In Application.
    • Ensure that Survey1App is selected in the drop-down menu.
    • In WAR Display Name, key Survey1WAR.
    • Click Edit.
    • Select the files needed for the component. These are:
      • The images directory
      • index.jsp
      • RockSurvey.jsp
      • RockSurveyExit.jsp
      • SurveyWelcome.jsp
    • Click Add; click OK; click Next.
    • Select JSP for the type of Web component, then click Next.
    • In the JSP filename combo box, select index.jsp.
    • In Web Component Name, keep the default -- index.
    • Click Next. There are no Initialization Parameters. Click Next.
    • Under Component Aliases, click Add, enter "/index" (without quotes).
    • Click Next; click Finish.
    • Survey1WAR now appears under Survey1App.
    • Select Survey1WAR, then select the EJB Refs tab and click Add.
    • When it invokes the lookup() method, the Web client refers to the home of an enterprise bean like this:
      Object objRef = ic.lookup("java:comp/env/ejb/RockSurveyBean");
      So, in the Coded Name column, enter ejb/RockSurveyBean.
    • Accept Session for Type and Remote for Interface.
    • For Home Interface, enter RockSurveyRemoteHome.
    • In the Local/Remote Interface column, enter RockSurveyRemote.
    • At the bottom, select ejb-jar-ic.jar#RockSurvey1Bean from the Enterprise Bean Name drop-down menu. Then click the JNDI Name button and select ejb/RockSurvey1Bean from the drop-down menu. This maps the actual JNDI name for the bean to the name used in the application code.
  6. Deployment. We can now actually deploy the application:
    • Select Survey1App.
    • Select Tools, Deploy from the menu.
    • Under Object To Deploy, ensure that Survey1App is selected.
    • Under Target Server, we are using localhost. Be sure that is selected.
    • Select the Return Client JAR check box.
    • Select Save object before deploying.
    • Click Next.
    • Verify that the JNDI name under both Application and References is ejb/RockSurvey1Bean, then click Next.
    • Key "/Survey1App" in the context root.
    • Click Next, then Finish.

The Deployment Progress dialog will display. Wait until the progress bars are complete and the Cancel button changes to OK, then click OK. The Survey1App application is deployed.

If you look in the Survey1 folder, you will see that it now contains Survey1App.ear and Survey1AppClient.jar. To run the Survey1App, start your browser and enter http://localhost:8000/Alice as the target URL. When the Alice's World home page appears, click on the "Alice's Surveys - Take 1" link. The JSP page must be compiled, so the first run will appear to take a while to load. You can also run the application by using the URL http://localhost:8000/Survey1App, but the Done button expects to return to the home page, so you should start from there.

No comments: