6.3. The Life Cycle of an Entity Bean
To understand how to best develop entity beans, it is important to understand how the container manages them. The EJB specification defines just about every major event in an entity bean's life, from the time it is instantiated to the time it is garbage collected. This is called the life cycle, and it provides the bean developer and EJB vendors with all the information they need to develop beans and EJB servers that adhere to a consistent protocol. To understand the life cycle, we will follow an entity instance through several life-cycle events and describe how the container interacts with the entity bean during these events. Figure 6-2 illustrates the life cycle of an entity instance.
Figure 6-2. Entity bean life cycle
We will examine the life cycle of an entity bean and identify the points at which the container would call each of the methods described in the EntityBean interface. Bean instances must implement the EntityBean interface, which means that invocations of the callback methods are invocations on the bean instance itself.
6.3.1. Does Not Exist
The bean instance begins life as a collection of files. Included in that collection are the bean's deployment descriptor, the remote interface, the home interface, the primary key, and all the supporting classes generated at deployment time. At this stage, no instance of the bean exists.
6.3.2. The Pooled State
When the EJB server is started, it reads the bean's files and instantiates several instances of the bean, which it places in a pool. The instances are created by calling the Class.newInstance() method on the bean class. The newInstance() method creates an instance using the default constructor, which has no arguments. This means that the persistent fields of the bean instances are set at their default values; the instances themselves do not represent any data in the database.
Immediately following the creation of an instance, and just before it is placed in the pool, the container assigns the instance its EntityContext. The EntityContext is assigned by calling the setEntityContext() method defined in the EntityBean interface and implemented by the bean class. After the instance has been assigned its context, it is entered into the instance pool.
In the instance pool, the bean instance is available to the container as a candidate for serving client requests. Until it is requested, however, the bean instance remains inactive unless it is used to service a find request. Bean instances in the Pooled state typically service find requests, which makes perfectly good sense because they aren't busy and find methods don't rely on the bean instance's state. All instances in the Pooled state are equivalent. None of the instances are assigned to an EJB object, and none of them has meaningful state.
At each stage of the entity bean's life cycle the bean container provides varying levels of access. For example, the EJBContext.getPrimary() method will not work if it's invoked during in the ejbCreate() method, but it does work when called in the ejbPostCreate() method. Other EJBContext methods have similar restrictions, as does the JNDI ENC (EJB 1.1). While this section touches on the accessibility of these methods, a complete table that details what is available in each bean class method (ejbCreate(), ejbActivate(), ejbLoad(), etc.) can be found in Appendix B, "State and Sequence Diagrams".
6.3.3. The Ready State
When a bean instance is in the Ready State, it can accept client requests. A bean instance moves to the Ready State when the container assigns it to an EJB object. This occurs under two circumstances: when a new entity bean is being created or when the container is activating an entity.
188.8.131.52. Transitioning from the Pooled state to the Ready State via creation
When a client application invokes the create() method on an EJB home, several operations must take place before the EJB container can return a remote reference (EJB object) to the client. First, an EJB object must be created on the EJB server. Once the EJB object is created, a bean instance is taken from the instance pool and assigned to the EJB object. Next, the create() method, invoked by the client, is delegated to its corresponding ejbCreate() method on the bean instance. After the ejbCreate() method completes, a primary key is created. In container-managed persistence, the container instantiates and populates the key automatically; in bean-managed persistence, the bean instantiates and populates the primary key and returns it from the ejbCreate() method. The key is embedded in the EJB object, providing it with identity. Once the EJB object has identity, the bean instance's EntityContext can access information specific to that EJB object, including the primary key and its own remote reference. While the ejbCreate() method is executing, the properties, security, and transactional information is available.
When the ejbCreate() method is done, the ejbPostCreate() method on the bean instance is called. At this time, the bean instance can perform any post-processing that is necessary before making itself available to the client. While the ejbPostCreate() executes, the bean's primary key and access to its own remote reference is available through the EJBContext.
Finally, after the successful completion of the ejbPostCreate() method, the home is allowed to return a remote reference--an EJB object stub--to the client. The bean instance and EJB object are now ready to service method requests from the client. This is one way that the bean instance can move from the Pooled state to the Ready State.
184.108.40.206. Transitioning from the Pooled state to the Ready State via a find method
When a find method is executed, each bean that is found will be realized by transitioning an instance from the Pooled state to the Ready State. When a bean is found, it is assigned to an EJB object and its remote reference is returned to the client. A found bean follows the same protocol as a passivated bean; it is activated when the client invokes a business method. A found bean can be considered to be a passivated bean and will move into the Ready State through activation as described in the next section.
220.127.116.11. Transitioning from the Pooled state to the Ready State via activation
The activation process can also move a bean instance from the Pooled state to the Ready State. Activation facilitates resource management by allowing a few bean instances to service many EJB objects. Activation was explained in Chapter 2, "Architectural Overview", but we will revisit the process as it relates to the bean instance's life cycle. Activation presumes that the bean has previously been passivated. More is said about this state transition later; for now, suffice it to say that when a bean instance is passivated, it frees any resources that it does not need and leaves the EJB object for the instance pool. When the bean instance returns to the pool, the EJB object is left without an instance to delegate client requests to. The EJB object maintains its stub connection on the client, so as far as the client is concerned, the entity bean hasn't changed. When the client invokes a business method on the EJB object, the EJB object must obtain a new bean instance. This is accomplished by activating a bean instance.
When a bean instance is activated, it leaves the instance pool (the Pooled State) to be assigned to an EJB object. When a bean instance makes this transition, the bean instance is first assigned to an EJB object. Once assigned to the EJB object, the ejbActivate() method is called--the instance's EntityContext can now provide information specific to the EJB object, but it cannot provide security or transactional information. This callback method can be used in the bean instance to reobtain any resources or perform some other work needed before servicing the client.
When an entity bean instance is activated, nonpersistent fields contain arbitrary values (dirty values) and must be reinitialized in the ejbActivate() method.
In container-managed persistence, persistent fields (container-managed fields) are automatically synchronized with the database after ejbActivate() is invoked and before a business method can be serviced by the bean instance. The order in which these things happen is as follows:
In bean-managed persistence, persistent fields are synchronized by the ejbLoad() method after ejbActivate() has been called and before a business method can be invoked. Here is the order of operations in bean-managed persistence:
18.104.22.168. Transitioning from the Ready State to the Pooled state via passivation
A bean can move from the Ready State to the Pooled state via passivation, which is the process of disassociating a bean instance from an EJB object when it is not busy. After a bean instance has been assigned to an EJB object, the EJB container can passivate the instance at any time, provided that the instance is not currently executing a method. As part of the passivation process, the ejbPassivate() method is invoked on the bean instance. This callback method can be used in the instance to release any resources or perform other processing prior to leaving the EJB object. A bean-managed entity instance should not try to save its state to the database in the ejbPassivate() method; this activity is reserved for the ejbStore() method. The container will invoke ejbStore() to synchronize the bean instance's state with the database prior to passivating the bean. When ejbPassivate() has completed, the bean instance is disassociated from the EJB object server and returned to the instance pool. The bean instance is now back in the Pooled State.
The most fundamental thing to remember is that, for entity beans, passivation is simply a notification that the instance is about to be disassociated from the EJB object. Unlike stateful session beans, an entity bean instance's fields are not serialized and held with the EJB object when the bean is passivated. Whatever values are held in the instance's fields when it was assigned to the EJB object will be carried with it to its next assignment.
22.214.171.124. Transitioning from the Ready State to the Pooled state via removal
A bean instance also moves from the Ready State to the Pooled state when it is removed. This occurs when the client application invokes one of the remove() methods on the bean's EJB object or EJB home. With entity beans, invoking a remove method means that the entity's data is deleted from the database. Once the entity's data has been deleted from the database, it is no longer a valid entity. The EntityContext can provide the EJB object with identity information during the execution of the ejbRemove() method. Once the ejbRemove() method has finished, the bean instance is moved back to the instance pool and out of the Ready State. It is important that the ejbRemove() method release any resources that would normally be released by ejbPassivate() , which is not called when a bean is removed. This can be done by invoking the ejbPassivate() method within the ejbRemove() method body.
6.3.4. Life in the Ready State
A bean is in the Ready State when it is associated with an EJB object and is ready to service requests from the client. When the client invokes a business method, like Ship.getName(), on the bean's remote reference (EJB object stub), the method invocation is received by the EJB object server and delegated to the bean instance. The instance performs the method and returns the results. As long as the bean instance is in the Ready State, it can service all the business methods invoked by the client. Business methods can be called zero or more times in any order.
The ejbLoad() and ejbStore() methods, which synchronize the bean instance's state with the database, can be called only when the bean is in the Ready State. These methods can be called in any order, depending on the vendor's implementation. Some vendors call ejbLoad() before every method invocation and ejbStore() after every method invocation, depending on the transactional context. Other vendors call these methods less frequently.
In bean-managed persistence, the ejbLoad() method should always use the EntityContext.getPrimaryKey() to obtain data from the database and not trust any primary key or other data that the bean has stored in one of its fields. (This is how we implemented it in the bean-managed version of the Ship bean.) It should be assumed, however, that the state of the bean is valid when calling the ejbStore() method.
In container-managed persistence, the ejbLoad() method is always called immediately following the synchronization of the bean's container-managed fields with the database--in other words, right after the container updates the state of the bean instance with data from the database. This provides an opportunity to perform any calculations or reformat data before the instance can service business method invocations from the client. The ejbStore() method is called just before the database is synchronized with the state of the bean instance--just before the container writes the container-managed fields to the database. This provides the container-managed bean instance with an opportunity to change the data in the container-managed fields prior to their persistence to the database.
In bean-managed persistence, the ejbLoad() and ejbStore() methods are called when the container deems it appropriate to synchronize the bean's state with the database. These are the only callback methods that should be used to synchronize the bean's state with the database. Do not use ejbActivate(), ejbPassivate(), setEntityContext(), or unsetEntityContext() to access the database for the purpose of synchronization. The ejbCreate() and ejbRemove() methods, however, can be used to insert and delete (respectively) the entity's data from the database.
6.3.5. End of the Life Cycle
A bean instance's life ends when the container decides to remove it from the pool and allow it to be garbage collected. This happens under a few different circumstances. If the container decides to reduce the number of instances in the pool--usually to conserve resources--it releases one or more bean instances and allows them to be garbage collected. The ability to adjust the size of the instance pool allows the EJB server to manage its resources--the number of threads, available memory, etc.--so that it can achieve the highest possible performance. This behavior is typical of a CTM.
When an EJB server is shut down, most containers will release all the bean instances so that they can be safely garbage collected. Finally, some containers may decide to release an instance that is behaving unfavorably or an instance that has suffered from some kind of unrecoverable error that makes it unstable. A bean could be behaving unfavorably if, for example, it has a bug that causes it to enter an endless loop or create an unusually large number of objects.
When a entity bean instance leaves the instance pool to be garbage collected, the unsetEntityContext() method is invoked by the container to alert the bean instance that it is about be destroyed. This callback method lets the bean release any resources it maintains in the pool and dereference the EntityContext (usually by setting it to null). Once the bean's unsetEntityContext() method has been called and it is removed from the pool, it will be garbage collected.
The bean instance's finalize() method may or may not be invoked following the unsetEntityContext() method. A bean should not rely on its finalize() method, since each vendor will handle dereferenced instances differently: some may garbage collect an instance immediately, and others may postpone garbage collection indefinitely.
Copyright © 2001 O'Reilly & Associates. All rights reserved.