Session BeansIntroduction Session beans are used to manage the interactions of entity and other session beans, access resources, and generally perform tasks on behalf of the client. Session beans correspond to the controller in a model-view-controller architecture because they encapsulate the business logic of a three-tier architecture. There are two basic kinds of session bean: stateless and stateful. Stateless session beans are made up of business methods that behave like procedures; they operate only on the arguments passed to them when they are invoked. Stateless beans are called stateless because they are transient; they do not maintain business state between method invocations. Stateful session beans encapsulate business logic and state specific to a client. Stateful beans are called "stateful" because they do maintain business state between method invocations, held in memory and not persistent. Stateless session beans Stateless session beans, like all session beans, are not persistent business objects as are entity beans. They do not represent data in the database. Instead, they represent business processes or tasks that are performed on behalf of the client using them. The business methods in a stateless bean act more like traditional procedures in a legacy transaction-processing monitor. Each invocation of a stateless business method is independent from previous invocations. Because stateless session beans are stateless, they are easier for the EJB container to manage, so they tend to process requests faster and use less resources. But this performance advantage comes at a price: stateless session beans are stupid. They don't remember anything from one method invocation to the next. An example of a stateless session bean is a CreditService bean, representing a credit service that can validate and process credit card charges. A hotel chain might develop a CreditService bean to encapsulate the process of verifying a credit card number, making a charge, and recording the charge in the database for accounting purposes. Below are the remote and home interfaces for the CreditService bean:
// remote interface
public interface CreditService extends javax.ejb.EJBObject { public void verify(CreditCard card, double amount) throws RemoteException, CreditServiceException; public void charge(CreditCard card, double amount) throws RemoteException, CreditServiceException; } // home interface public interface CreditServiceHome extends java.ejb.EJBHome { public CreditService create() throws RemoteException, CreateException; } The remote interface, CreditService, defines two methods, verify() and charge(), which are used by the hotel to verify and charge credit cards. The hotel might use the verify() method to make a reservation, but not charge the customer. The charge() method would be used to charge a customer for a room. The home interface, CreditServiceHome provides one create() method with no arguments. All home interfaces for stateless session beans will define just one method, a no-argument create() method, because session beans do not have find methods and they cannot be initiated with any arguments when they are created. Stateless session beans do not have find methods, because stateless beans are all equivalent and are not persistent. In other words, there is no unique stateless session beans that can be located in the database. Because stateless session beans are not persisted, they are transient services. Every client that uses the same type of session bean gets the same service. Below is the bean class definition for the CreditService bean. This bean encapsulates access to the Acme Credit Card processing services. Specifically, this bean accesses the Acme secure Web server and posts requests to validate or charge the customer's credit card.
import javax.ejb.SessionBean;
public class CreditServiceBean implements SessionBean { URL acmeURL; HttpURLConnection acmeCon; public void ejbCreate() { try { InitialContext jndiContext = new InitialContext(); URL acmeURL = (URL) jndiContext.lookup("java:comp/ejb/env/url/acme"); acmeCon = acmeURL.openConnection(); } catch (Exception e) { throws new EJBException(e); } } public void verify(CreditCard card, double amount) { String response = post("verify:" + card.postString() + ":" + amount); if (response.substring("approved")== -1) throw new CreditServiceException("denied"); } public void charge(CreditCard card, double amount) throws CreditCardException { String response = post("charge:" + card.postString() + ":" + amount); if (response.substring("approved")== -1) throw new CreditServiceException("denied"); } private String post(String request) { try { acmeCon.connect(); acmeCon.setRequestMethod("POST "+request); String response = acmeCon.getResponseMessage(); } catch (IOException ioe) { throw new EJBException(ioe); } } public void ejbRemove() { acmeCon.disconnect(); } public void setSessionContext(SessionContext cntx) {} public void ejbActivate() {} public void ejbPassivate() {} } The CreditService stateless bean demonstrates that a stateless bean can represent a collection of independent but related services. In this case, credit card validation and charges are related but not necessarily interdependent. Stateless beans might be used to access databases or unusual resources, as is the case with the CreditService bean, or to perform complex computations. The CreditService bean is used as an example in this tutorial to demonstrate the service nature of a stateless bean and to provide a context for discussing the behavior of the call back methods. The use of a stateless session beans is not limited to the behavior illustrated in this example; stateless beans can be used to perform any kind of service. The CreditServiceBean class uses a URL resource factory (acmeURL()) to obtain and maintain a reference to the Acme Web server which exists on another computer very far away. The CreditServiceBean uses the acmeURL() to obtain a connection to the Web server and post requests for the validation and charge of credit cards. The CreditService bean is used by clients instead of a direct connection so that the service can be better managed by the EJB container, which will pool connections and managed transactions and security automatically for the EJB client. The ejbCreate() method is invoked at the beginning of its lifetime and is invoked only once. The ejbCreate() method is a convenient method for initiating resource connections and variables that will be of use to the stateless bean for its lifetime. In the example above, the CreditServiceBean uses the ejbCreate() to obtain a reference to the HttpURLConnection factory, which it will use throughout its lifetime to obtain connections to the Acme Web server. The CreditServiceBean uses the JNDI ENC to obtain a URL connection factory in the same way that the CustomerBean used the JNDI ENC to obtain a DataSource resource factory for JDBC connections. The JNDI ENC is a default JNDI context that all beans have access to automatically. The JNDI ENC is used to access static properties, other beans, and resource factories like the java.net.URL and JDBC javax.sql.DataSource . In addition, the JNDI ENC also provides access to JavaMail and Java Messaging Service resource factories. The ejbCreate() and ejbRemove() methods are each only invoked once in its lifetime by the container; when the bean is first created and when it's finally destroyed. Invocations of the create() and remove() methods on its home and remote interfaces by the client do not result in invocations of the ejbCreate() and ejbRemove() methods on the bean instance. Instead, an invocation of the create() method provides the client with a reference to the stateless bean type and the remove() methods invalidate the reference. The container will decide when bean instances are actually created and destroyed and will invoke the ejbCreate() and ejbRemove() methods at these times. This allows stateless instances to be shared between many clients without impacting the clients' references. In the CreditServiceBean, the ejbCreate() and ejbRemove() methods are used to obtain a URL connection at the beginning the bean instance's life and to disconnect from it at the end of the bean instance's life. Between the times that the URL connection is obtained and disconnected it is used by the business methods. This approach reduces the number of times that a connection needs to be obtained and disconnected, conserving resources. It may seem wasteful to maintain the URL connection between method invocations, but stateless session beans are designed to be shared between many clients, so that they are in constant use. As soon as a stateless instance completes a method invocation for a client, it can immediately service another client. Ideally, there is little down time for a stateless session bean instance, so it makes sense to maintain the URL connection. The verify() and charge() methods delegate their requests to the post() method, a private helper method. The post() method uses an HttpURLConnection to submit the credit card information to the Acme Web server and return the reply to the verify() or charge() method. The HttpURLConnection may have been disconnected automatically by the container -- this might occur if, for example, a lot of time elapsed since its last use -- so the post() method always invokes the connect() method, which does nothing if the connection is already established. The verify() and charge() methods parse the return value looking for the substring "approved," which indicates that the credit card was not denied. If "approved" was not found, it's assumed that the card was denied and a business exception is thrown. The setSessionContext() method provides the bean instance with a reference to the SessionContext, which serves the same purpose as the EntityContext did for the CustomerBean in the panel on . The SessionContext is not used in this example. The ejbActivate() and ejbPassivate() methods are not implemented in the CreditService bean because passivation is not used in stateless session beans. These methods are defined in the javax.ejb.SessionBean interface for the stateful session beans, and so an empty implementation must be provided in stateless session beans. Stateless session bean will never provide anything but empty implementations of these methods. Stateless session beans can also be used to access the database as well as coordinate the interaction of other beans to accomplish a task. Below is the definition of the HotelClerkBean shown earlier in this tutorial:
import javax.ejb.SessionBean;
import javax.naming.InitialContext; public interface HotelClerkBean implements SessionBean { InitialContext jndiContext; public void ejbCreate() {} public void reserveRoom(Customer cust, RoomInfo ri, Date from, Date to) { CreditCard card = cust.getCreditCard(); RoomHome roomHome = (RoomHome) getHome("java:comp/env/ejb/RoomEJB", RoomHome.class); Room room = roomHome.findByPrimaryKey(ri.getID()); double amount = room.getPrice(from,to); CreditServiceHome creditHome = (CreditServiceHome) getHome( "java:comp/env/ejb/CreditServiceEJB", CreditServiceHome.class); CreditService creditAgent = creditHome.create(); creditAgent.verify(card, amount); ReservationHome resHome = (ReservationHome) getHome( "java:comp/env/ejb/ReservationEJB", ReservationHome.class); Reservation reservation = resHome.create(cust.getName(), room,from,to); } public RoomInfo[] availableRooms(Location loc, Date from, Date to) { // do a SQL call to find available rooms Connection con = db.getConnection(); Statement stmt = con.createStatement(); ResultSet results = stmt.executeQuery("SELECT ..."); ... return roomInfoArray; } private Object getHome(String path, Class type) { Object ref = jndiContext.lookup(path); return PortableRemoteObject.narrow(ref,type); } } The HotelClerkBean is also a stateless bean. All the information needed to process a reservation or to query a list of available rooms is obtained from the method arguments. In the reserveRoom() method, operations on several other beans (Room, CreditService, and Reservation) are coordinated to accomplish one larger task, reserving a room for a customer. This is an example of a session bean managing the interactions of other beans on behalf of the client. The availableRooms() method is used to query the database and obtain a list of rooms -- the information is returned to the client as a collection of data wrappers defined by the RoomInfo class. The use of this class, shown below, is a design pattern that provides the client with a light-weight wrapper of just the information needed:
public class RoomInfo {
public int id; public int bedCount; public boolean smoking; } To obtain a reference to another bean, as is done three times in the reserveRoom() method, the private getHome() helper method is used. The getHome() method uses the JNDI ENC to obtain references to other beans:
private Object getHome(String path, Class type) {
Object ref = jndiContext.lookup(path); return PortableRemoteObject.narrow(ref,type); } In the EJB 1.1 specification, RMI over IIOP is the specified programming model, so CORBA references types must be supported. CORBA references cannot be cast using Java language native casting. Instead the PortableRemoteObject.narrow() method must be used to explicitly narrow a reference from one type to its subtype. Because JNDI always returns an Object type, all bean references should be explicitly narrowed to support portability between containers. Stateful session beans Stateful session beans are dedicated to one client and maintain conversational state between method invocations. Unlike stateless session beans, clients do not share stateful beans. When a client creates a stateful bean, that bean instance is dedicated to service only that client. This makes it possible to maintain conversational state, which is business state that can be shared by methods in the same stateful bean. As an example, the HotelClerk bean can be modified to be a stateful bean which can maintain conversational state between method invocations. This would be useful, for example, if you want the HotelClerk bean to be able to take many reservations, but then process them together under one credit card. This occurs frequently, when families need to reserve two or more rooms or when corporations reserve a block of rooms for some event. Below the HotelClerkBean is modified to be a stateful bean:
import javax.ejb.SessionBean;
import javax.naming.InitialContext; public class HotelClerkBean implements SessionBean { InitialContext jndiContext; //conversational-state Customer cust; Vector resVector = new Vector(); public void ejbCreate(Customer customer) {} cust = customer; } public void addReservation(Name name, RoomInfo ri, Date from, Date to) { ReservationInfo resInfo = new ReservationInfo(name,ri,from,to); resVector.addElement(resInfo); } public void reserveRooms() { CreditCard card = cust.getCreditCard(); Enumeration resEnum = resVector.elements(); while (resEnum.hasMoreElements()) { ReservationInfo resInfo = (ReservationInfo) resEnum.nextElement(); RoomHome roomHome = (RoomHome) getHome("java:comp/env/ejb/RoomEJB", RoomHome.class); Room room = roomHome.findByPrimaryKey(resInfo.roomInfo.getID()); double amount = room.getPrice(resInfo.from,restInfo.to); CreditServiceHome creditHome = (CreditServiceHome) getHome("java:comp/env/ejb/CreditServiceEJB", CreditServiceHome.class); CreditService creditAgent = creditHome.create(); creditAgent.verify(card, amount); ReservationHome resHome = (ReservationHome) getHome("java:comp/env/ejb/ReservationEJB", ReservationHome.class); Reservation reservation = resHome.create(resInfo.getName(), resInfo.roomInfo,resInfo.from,resInfo.to); } public RoomInfo[] availableRooms(Location loc, Date from, Date to) { // Make an SQL call to find available rooms } private Object getHome(String path, Class type) { Object ref = jndiContext.lookup(path); return PortableRemoteObject.narrow(ref,type); } } In the stateful version of the HotelClerkBean class, the conversational state is the Customer reference, which is obtained when the bean is created, and the Vector of ReservationInfo objects. By maintaining the conversational state in the bean, the client is absolved of the responsibility of keeping track of this session state. The bean keeps track of the reservations and processes them in a batch when the serverRooms() method is invoked. To conserve resources, stateful session beans may be passivated when they are not in use by the client. Passivation in stateful session beans is different than for entity beans. In stateful beans, passivation means the bean conversational-state is written to a secondary storage (often disk) and the instance is evicted from memory. The client's reference to the bean is not affected by passivation; it remains alive and usable while the bean is passivated. When the client invokes a method on a bean that is passivated, the container will activate the bean by instantiating a new instance and populating its conversational state with the state written to secondary storage. This passivation/activation process is often accomplished using simple Java serialization but it can be implemented in other proprietary ways as long as the mechanism behaves the same as normal serialization. (One exception to this is that transient fields do not need to be set to their default initial values when a bean is activated.) Stateful session beans, unlike stateless beans, do use the ejbActivate() and ejbPassivate() methods. The container will invoke these methods to notify the bean when it's about to be passivated (ejbPassivate()) and immediately following activation ejbActivate()). Bean developers should use these methods to close open resources and to do other clean-up before the instance's state is written to secondary storage and evicted from memory. The ejbRemove() method is invoked on the stateful instance when the client invokes the remove() method on the home or remote interface. The bean should use the ejbRemove() method to do the same kind of clean-up performed in the ejbPassivate() method. |