| |
Kevin Boone, Web-Tomorrow
This article describes the lifecycles of the various types of
enterprise JavaBean (EJB). It is intended as a guide to help EJB
developers understand exactly how their EJBs will be interacting with
the EJB container. It aims also to clear up some of the widespread
misunderstandings about which EJB methods get called in which
circumstances. This document is based on the 1.1 version of the EJB
specification.
Session EJB
Session EJBs are intended to represent some kind of service provided to a client, rather than being a model of some entity in the business domain. Session EJBs have a relatively short lifespan, usually no longer than the duration of a particular client-provider interaction. The EJB specification does not stipulate that the state of an EJB should be able to persist in the event of a system failure, although some container providers may have a system to enable this. In any event, a session EJB will normally have a lifespan measured in seconds or minutes, rather that days or years.
Session EJBs come in two varieties: stateful and stateless. The stateless variety is the simpler, and it has absolutely no conversational state. From the client perspective, every interaction with a stateless session EJB must be complete and self-contained in a single method call. There is no guarantee that if the client calls a different method, or even the same method twice, the method will be called on the same instance of the EJB class. In practice, the container will normally maintain a pool of stateless session beans, and each method call will be dispatched to a free instance in the pool.
Stateful session EJBs do have a conversational state; once instantiated each instance is associated with a particular client. The client does have create/destroy control over the EJB instance, but the container may also destroy the instance if it detects that it is unused.
Whether a session EJB is stateful or stateless, the EJB specification dictates that any instance of the EJB can only serve one client at a time. That is, session EJBs are (by design) not re-entrant. If a client gets the remote interface to a session EJB, and calls methods on it in two different threads, this will result in an exception being thrown. The two threads should each get a reference to a remote interaface by calling create on the home interface.
Stateless session EJB
The lifecycle of a stateless session EJB is shown in the state diagram below. Note that these states
are of the EJB instance loaded in the container.
Stateless session EJBs are not associated with a particular client. The container can maintain a pool of them, and direct method invocations to any free instances. Therefore, creation and deletion of instances has little connection with the client. When a client calls create() on the home interface, the container need not do anything in particular to any EJB instance. The container must ensure that, when a business method is called, there is at least one instance in the pool. Similarly, the container will delete instances when it sees fit.
Stateless session EJBs cannot be activated or passivated (as they have no state to save). They cannot take part in transactions in any sense that requires programmatic support (e.g., they cannot implement
SessionSynchronization because there is no code to synchronize. Therefore, the stateless session has a very simple lifecycle.
Stateful session EJB
In sharp contrast, the stateful session EJB probably has the most complex lifecycle. This is because it provides programmatic support for transaction monitoring via the SessionSynchronization
interface.
The stateful session EJB is associated with a particular client, and will be created and destroyed
(indirectly) at the behest of the client. Moreover, because it may persist for some time, it provides an activation/passivation mechanism. Note that the instance may be activated to serve a different client than the one it was talking to when it was passivated. This means that the passivate method must leave the EJB in a state at which the container can serialize it.
Method calls may be transactional or non-transactional (according to the deployment descriptor). A non-transactional method can be satisfied immediately, and does not involve the SessionSynchronization methods. Note that the container can activate a passivated session to serve a particular method call, if there is no active session currently available.
If a method is transactional, then the EJB container interacts with a transaction manager. Transactions
are committed when the method returns, but bear in mind that the method may call other methods, but
the SessionSynchronization methods are only called at the beginning and end of a complete transaction.
Entity EJB
The entity bean represents some form of persistent entity in the business domain, and very often maps onto a row of a database table. In the state diagram below, the lifecycle shown is that of the EJB
instance in the container; the database row with which it may be associated has a separated lifecycle.
Note that entity EJBs do not have a conversational state with a client. They are persistent, but not sessional. Thus the container can (and probably will) maintain a pool of EJBs, and associate each with a particular database item when referenced by a client. The ejbActivate() and ejbPassivate() methods are called when the container has changed the mapping of the instance onto the databse; unlike with session EJBs, the entity need not free resources when passivated, or reclaim them
when activated.
The container associates the instance with a data item when the client calls create(...)
on the home interface, or calls a business method on the remote interface. It does not
do so when the client calls findByPrimaryKey(), or any other finder method. Note on the diagram that the `findXXX' event does not cause a transition to the `ready' state.
Although entity EJB methods may be transactional, there is no programmatic control of transactions, andtransactional progress is not part of the lifecycle.
When an EJB instance is in the `ready' state, the container will call ejbLoad() and
ejbStore() where necessary to store and load the instance variables to the persistent
store. The sequence and frequency with which it does this depends on the `commit option' supported by the container. Typically the container will call ejbLoad() before each business method,
and ejbStore() after.
EJB gotchas
EJB development is complicated by the architecture in which an EJB operates. Here are some of the
common problems that developers should be aware of. A familiarity with the lifecycle of each of the different EJB types can help to avoid making the following mistakes.
Re-entrancy
SessionBeans are intentionally non-reentrant; the EJB container will thow a RemoteException if a client tries to call methods on the same remote interface with two different threads. Given that a session bean is intended to represent a conversational interaction with a single client, the specification that they be nonre-entrant is logical. Entity beans can, however, be re-entrant if so specified.
If an entity bean is specified as being re-entrant, then multiple threads will be allowed concurrent access to the same methods in the same instances. However, the specification recommends that this not be allowed, unless it is essential.
Inconsistent use of terms
In the EJB specification, and the EJB API, the same terms are used to mean somewhat different things. A good example is the use of the terms `activation' and `passivation';
these terms mean different things to session and entity EJBs. For a session EJB, the container calls ejbPassivate() to reclaim resources that the session may be holding. The EJB should respond by freeing resources, but in such a manner that they can be reinstated when ejbActivate is called. An entity EJB, on the other hand, is created initially in the passive state. It interprets this to mean that the bean instance is not assoicated with any particular business data (i.e., no particular database row). The container calls ejbActivate() when the association is made between the EJB instance and the business data. In entity beans, activation and passivation are only loosely associated with resource management.
Widespead misconceptions about the specification
The EJB specification has gone through several revisions, and many people are still using code that complied strictly with earlier versions, and does not comply with recent revisions. For example, the current version of the EJB 1.1 specification (page 52) specifically states that an EJB must not store its context in a transient instance variable. And yet, lines like this:
private transient EntityContext ctx;
appear at the start of many EJB classes. Here are some similar examples.
- There is no counterpart to the method
setSessionContext in the
SessionBean interface. That is, there is no `unsetSesssionContext()'. This is confusing
because there is an unsetEntityContext to correspond with setEntityContext. If the developer provides an unsetSessionContext in the code, it will never be called.
- Stateless session beans are never passivated; the
ejbActivate() and
ejbActivate() methods are never called. There is no reason why they should be; if the
container wants to free resources held by stateless EJBs, it can just delete them.
- The
ejbFindByPrimaryKey() method does not cause an EJB instance to become associated
with a particular database row; it merely verifies that the row exists. The method does not have to
do anything except return the primary key passed as a parameter, or throw an exception. It does not have to load the instance variable from the database (it won't hurt, but it's a waste of time).
Use of the same interfaces to specify behaviour in logically different classes
When implementing an EJB class, the code will specify that the class impements one of the EJB interfaces, either SessionBean or EntityBean. The fact that these interfaces are implemented means that the specified methods must be provided by the developer. However, there is no guarantee that any of them will be called. The needs of a stateless session EJB are somewhat different to those of a stateful session, but they implement the same interfaces. In addition, the EJB must provide other methods which are not specified in the interface, but will be called (e.g., ejbCreate()). The section below summarises the methods that must be implemented in each of the EJB types, and when they will be called, if ever.
Summary of EJB-class method requirements
When implementing an EJB class, the code will specify that the class impements one of the EJB interfaces, either SessionBean or EntityBean. The fact that these interfaces are implemented means that the specified methods must be provided by the developer. However, there is no guarantee that any of them will be called. In addition, the EJB must provide other methods which are not specified in the interface, but will be called (e.g., ejbCreate()). The table below shows the methods that must be implemented in each of the EJB types, and when they will be called, if ever.
Stateless session bean (implements SessionBean)
|
ejbCreate()
|
Called when container wants to create an instance; not directly
linked to a client call
|
setSessionContext()
|
called by the container just before ejbCreate(). The EJB
should save a reference to the session context for future use.
|
ejbActivate()
|
Never called
|
ejbPassivate()
|
Never called
|
ejbRemove()
|
Called when container wants to remove the instance; not directly
linked to a client call
|
|
Stateful session bean (implements SessionBean, and optionally
SessionSynchronization)
|
ejbCreate(...)
|
Called when the client calls create on the home interface
|
setSessionContext()
|
called by the container just before ejbCreate(). The EJB
should save a reference to the session context for future use.
|
ejbActivate()
|
Called by the container when any method is called and the EJB is in the
`passive' state.
|
ejbPassivate()
|
Called by the container when resources are to be reclaimed. The EJB
specification dictates that this method must leave the instance in a state
ready to be serialized. In particular, it must close all open JDBC
connections.
|
ejbRemove()
|
Called when container wants to remove the instance; not directly
linked to a client call
|
afterBegin() (if specified)
|
Called when the container is about to execute a method in a new transactional
context. The EJB should save its state so that it can be recovered if a
transaction fails and rolls back.
|
beforeCompletion() (if specified)
|
Called when the container is about to request the transaction manager to
commit a transaction. This is the last chance that the EJB has to prevent the
transaction being committed (by calling setRollbackOnly).
|
afterCompletion(boolean commit) (if specified)
|
Called by the container when a transaction has completed. If the
commit flag if false, the transaction failed and the EJB should
restore the state it saved in afterBegin.
|
|
Entity bean with bean-managed persistence (implements EntityBean)
|
ejbCreate(...)
|
Called by the container when the client calls a create()
method. The EJB should initialize instance variables from the
parameters, and create a new
representation of the object in the persistent store
|
ejbPostCreate(...)
|
Called by the container directly after ejbCreate().
Anything done in this method could, in principle be done
in ejbCreate(), as the two methods are always called
sequentially. However, the method must be implemented, even if
empty.
|
setEntityContext()
|
called by the container just before ejbCreate(). The EJB
should save a reference to the entity context for future use.
|
ejbActivate()
|
Called by the container when a business method is called and the EJB is
in the passive (pooled) state, or whenever the container has just
associated the instance with a particular database entry. This method
is very often empty, but must be implemented.
|
ejbPassivate()
|
Called by the container when it is about to dissociate the instance from
a particular database entry. This method is very often empty, but must
be implemented.
|
ejbLoad()
|
Called by the container when the instance should take on the identity of a
particular database item. The method should load the instance variables from
the persistent store, perhaps using JDBC calls
|
ejbStore()
|
Called by the container when the instance must write its identity to the
persistent store. The method should do this, using JDBC calls for example.
|
ejbRemove()
|
Called when container wants to remove the instance; not directly
linked to a client call. This method should remove the instance's
representation from the persistent store.
|
ejbFindByPrimaryKey()
|
Called by the container when the client calls findByPrimaryKey() on the
home interface. This method should simply verify that the primary key
supplied corresponds to a real database entry. It does not need to
set the instance variables (contrary to popular opinion).
Must be implemented.
|
ejbFindXXX()
|
Called by the container when the client calls findXXX() on the
home interface. This method should return a collection of primary key
class instances. Must be implemented if specified in the home
interface.
|
|
Entity bean with container-managed persistence (implements EntityBean)
|
ejbCreate(...)
|
Called by the container when the client calls a create()
method. The EJB should initialize instance variables
from the parameters and return `null'. The
container will create a corresponding database entry. At
least one ejbCreate(...) must be implemented, and should
match the signature in the home interface.
|
ejbPostCreate(...)
|
Called by the container directly after ejbCreate().
Anything done in this method could, in principle be done
in ejbCreate(), as the two methods are always called
sequentially. Must be implemented, to match the signature of
ejbCreate(), even if unused.
|
setEntityContext()
|
Called by the container just before ejbCreate(). The EJB
should save a reference to the entity context for future use.
|
ejbActivate()
|
Called by the container when a business method is called and the EJB is
in the passive (pooled) state, or whenever the container has just
associated the instance with a particular database entry. This method
is very often empty, but must be implemented.
|
ejbPassivate()
|
Called by the container when it is about to dissociate the instance from
a particular database entry. This method is very often empty,
but must be implemented.
|
ejbLoad()
|
Called by the container when the instance should take on the identity of a
particular database item. With CMP, normally empty, but must be implemented.
|
ejbStore()
|
Called by the container when the instance must write its identity to the
persistent store. With CMP, normally empty, but must be implemented.
|
ejbRemove()
|
Called when container wants to remove the instance; not directly
linked to a client call. Normally empty, but must be implemented.
|
ejbFindByPrimaryKey()
|
Never called; need not be implemented.
|
ejbFindXXX()
|
Never called; need not be implemented.
|
|
|