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.
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.
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.
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.[22] 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.
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.
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.
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.
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, 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:
ejbActivate()
is invoked on the bean instance.
ejbLoad()
is called to let the bean synchronize
its persistent fields.
Business methods are invoked as needed.
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.
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.
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.
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.
[22] Constructors should never be defined in the bean class. The default no-argument constructor must be available to the container.