| |
Inside the J2EE Reference Implementation
By Kevin Boone
August 2000
Disclaimer
I have produced this document for educational purposes, to accompany my
own classes and for others who are interested. Some of the features
it describes are specific to Sun's implementation of an EJB
server, and may change in subsequent releases of their J2EE product.
Moreover, different container vendors may implement things differently.
Therefore I don't recommend that direct use is made of any of the
proprietary classes or interfaces described. Please note that although
I do teach EJB technology for Sun, I am not involved with the
development of the J2EE product, and nothing in this document should be
construed as a statement of Company policy.
Introduction
In this document I explain in detail what is going on inside Sun's J2EE
Reference Implementation server when a simple EJB is deployed and a method
is executed. I assume familiarity with the the basic principles of EJB
technology, such as the use of the home and remote interfaces. I also
assume that the reader has some basic understanding of the J2EE product
itself, and its deployment tools. For many developers, the level of
detail exposed in this article is beyond what is required; however, a good way to develop a
really thorough understanding of EJB technology is to study exactly what
the client and the server are doing when an EJB is deployed and used.
A note on terminology
Because some terms are used differently by different authors, I will
clarify at the outset the way in which I will use these terms.
|
Implementation class, bean class
|
The class provided by the developer to implement the EJB's
business functionality
|
|
EJB object
|
The object on the EJB server that is an implementation of the
EJB's remote interface. Some people use the term `remote object'
for this, but `EJB object' is used in the EJB specification.
|
|
Home object
|
The object on the EJB container that is an implementation of the
EJB's home interface. This is often a subclass of the EJB
container itself, but it doesn't have to be.
|
|
Stub, transport stub
|
Any object that handles the client side of the client-server
communications protocol, if required.
|
|
Skeleton
|
Any object that handles the server side of the client-server
communications protocol, if required.
|
|
Remote stub
|
This often causes confusion; the `remote stub' implements the
client side (the `stub side') of the client-server
protocol to the EJB object (the remote
object). It is important to realize that, despite the name, the
remote stub is instantiated on the client, not the server.
|
|
Home stub
|
The class that implements the client side of the client-server
protocol to the home object.
|
|
Remote skeleton
|
The class that implements the server side of the client-server
protocol to the EJB object (`remote object).
Note that the J2EE RI implements the remote skeleton and the EJB
object as separate classes, but in some EJB servers these roles are
combined.
|
|
Home skeleton
|
The class that implements the server side of the client-server
protocol to the Home object.
Note that the J2EE RI implements the home skeleton and the home
object as separate classes, but in some EJB servers these roles are
combined.
|
The EJB
For purposes of illustration I will use one of the EJBs from the `bank'
tutorial example, available from the JavaSoft Web site.
The bank application actually has four EJBS: Manager,
Teller, Customer and Account; we will focus on the `Manager' EJB, as it is
the simplest. `Manager' is a stateless session bean. It is comprised of
the standard class and interfaces, as shown below.
|
Implementation class
|
bank.ManagerBean
|
|
Remote interface
|
bank.Manager
|
|
Home interface
|
bank.ManagerHome
|
Although the Manager bean has a number of business methods, I will assume
that the client will only call one: getAllCustomers(). This
method is specified, as usual, in the remote interface
bank.Manager, and implemented in
the bean implementation class bank.ManagerBean.
It will be the job of the EJB server to create the implementations of the
home and remote interfaces, along with the transport stubs. This happens
at deployment time.
Deployment
During the deployment process, the J2EE server and the
deployment tools automatically create supporting classes based on
the bean's implementation class, home interface, and
remote interface. These classes can be broadly
summarized as follows.
- `Container' classes; these are the actual
implementations of the developer-supplied home
interface and remote interface. They are created, complied and
instantiated
on the server, and control access to the instances of
the bean class itself.
- `Skeleton' classes; these classes handle the
server side of the client-server communications
protocol between the client and the `container'
classes; they are instantiated on the server.
- `Stub' classes; these classes handle the client
side of the communication. These are downloaded to the
client during deployment, and instantiated on the
client when it executes.
Note that during deployment the developer's home interface, remote
interface and bean class must also be copied to the server, as the
container will create implementations of the interfaces, and instantiate
the bean class on demand.
The server-side classes are packaged into a JAR whose
name starts with the application name, and which
includes a serial number. During deployment the skeleton, sub and
container classes will created as Java source files, compiled and
packaged. The intermediate files are then deleted.
The client-side classes are
packaged into a JAR whose name is chosen by operator
at deploy time.
For the Manager EJB, the classes created on the server are as follows.
bank.ManagerBeanHomeImpl
|
Home object that serves as the EJB container for
instances of ManagerBean. This class implements the
interface bank.ManagerHome, and extends
com.sun.ejb.containers.EJBHomeImpl. A simplified
version of this class is shown in listing 1 at the end of this document.
|
bank._ManagerBeanHomeImpl_Tie
|
This class implements a CORBA `skeleton'; it
mediates between the client's stub
(bank._ManagerHome_Stub) and the container
ManagerBeanHomeImpl
|
bank.ManagerBean_EJBObjectImpl
|
This class implements the EJB object (`remote
object') which will act as a proxy for the `real' bean
instance bank.ManagerBean. It
implements the remote interface
bank.Manager, and extends
com.sun.containers.StatelessSessionEJBObjectImpl.
This class will handle serialization of accesses to the bean,
creation and completion of transactions, and security validation.
A simplified version of this class is shown in listing 2.
|
bank._ManagerBean_EJBObjectImpl_Tie
|
This class implements a CORBA `skeleton'; it
mediates between the client's stub
(bank._Manager_Stub) and the EJB
object ManagerBean_EJBObjectImpl
|
Each of these classes will be described in more detail
as we examine the process of running a simple client
application.
After deployment, and any time the J2EE server is
started, the deployment descriptor for the EJB is
examined, and the JNDI name registered with the naming
service. In addition, a home object is instantiated.
This object (of class bank.ManagerBeanHomeImpl)
will manage all requests to create, find and delete Manager
EJBs. Note that Manager is a stateless session bean,
and the home object will create and delete instances of the bean class as
it sees fit; this creation and deletion is not controlled directly by the
client.
Execution
The EJB's methods are executed in response to method calls on the
client. The code in the client that is important from an EJB
perspective is quite simple (line numbers are for reference in the text
below):
1 InitialContext ic = new InitialContext();
2 Object lookup = ic.lookup("Manager");
3 ManagerHome home = (ManagerHome)
PortableRemoteObject.narrow(lookup, ManagerHome.class);
4 Manager m = home.create();
5 Collection c = m.getAllCustomers();
We will now consider in detail what happens when this
client code is executed. For simplicity, we will assume
that this is the first time it has been run, and no
other client has had occasion to use the `Manager'
EJB.
- 1. Get the initial naming context. Because we
haven't indicated otherwise, there will be no URL or naming context
factory associated with
this naming context. This means that when the lookup is
performed, it will be carried out using the default
naming context class
com.sun.jndi.cosnaming.CNCtx,
and the default URL
iiop://localhost:1050.
(Note that when J2EE starts up, it indicates that its
naming service is on port 1050). This default is appropriate if the client
and the EJB server are actually on the same host. If not, we will need to
instantiate InitialContext with the appropriate environment.
The naming context CNCtx handles the client side of
the CORBA naming service (`CoSNaming').
When the J2EE server started, it obtained name-object mappings from
information that was provided (in the deployment descriptor XML)
when the EJB was deployed.
- 2. Look up the home interface for the EJB using the CORBA
naming service on the specified server. The naming
context client essentially maps the information about the EJB known
to the naming service on the server, into a class that the client can
make method calls on. In this case, the object returned
by
lookup("Manager") is an instance of
the home stub class bank._ManagerHome_Stub. Notice how
this name is derived from the name of the home
interface bank.ManagerHome. Where is this
class? It was generated automatically by the J2EE
server when the bean was deployed, and returned in the
`Client JAR' file. The stub class has the following
signature:
public class bank._ManagerHome_Stub
extends javax.rmi.CORBA.Stub implements bank.ManagerHome
{
static java.lang.Class class$javax$ejb$EJBMetaData;
static java.lang.Class class$javax$ejb$RemoveException;
static java.lang.Class class$bank$Manager;
static java.lang.Class class$javax$ejb$CreateException;
static {};
public _ManagerHome_Stub();
public java.lang.String _ids()[];
static java.lang.Class class$(java.lang.String);
public bank.Manager create() throws javax.ejb.CreateException, java.rmi.RemoteException;
public javax.ejb.EJBMetaData getEJBMetaData() java.rmi.RemoteException;
public javax.ejb.HomeHandle getHomeHandle() throws java.rmi.RemoteException;
public void remove(java.lang.Object) throws java.rmi.RemoteException, javax.ejb.RemoveException;
public void remove(javax.ejb.Handle) throws
java.rmi.RemoteException, javax.ejb.RemoveException;
}
Note that this home stub class is a CORBA stub, and implements the
bean's home interface bank.ManagerHome. It
provides concrete implementations of methods like
create() that are specified in the home
interface, but does not implement any `real' functionality in them.
Instead, methods in the home stub will communicate
with the `home skeleton' on the server. This is the
class bank.ManagerBeanHomeImpl_Tie. This will delegate
method calls to the home object implementation
bank.ManagerHomeImpl on the server. It is this class that
will do the real work. The protocol used between the client and the EJB
server is IIOP (Internet intra-ORB protocol; ORB is an abbreviation for
Object Request Broker); the stub and the skeleton between them isolate the
technicalities of this protocol from the client and the server.
- 3. We now call
PortableRemoteObject.narrow()
to get an RMI-compatible stub that corresponds to the home stub
returned by the lookup operation. In this case, the
home stub is already RMI-compatible (we are using a
Java implementation of a CORBA client, after all), so
we get another instance of class
bank._ManagerHome_Stub. In other words,
narrow() has no effect here, and we could
have written
ManagerHome = (ManagerHome)ic.lookup ("Manager");
and skipped the narrowing step. However, this would
prevent the client from communicating with the EJB using other
transport protocols,
should we wish to do so in future. With or without the
call to narrow(), we are doing a narrowing
cast here; that is, we are casting something that
can be any subclass of java.lang.Object
(the defined return type for lookup())
into a ManagerHome. Some developers use the term `down-cast'
for this operation.
- 4. We call
create() on the home
interface.
- This actually results in a call to
bank._ManagerHome_Stub.create() (because
we cast an object of this class into a
ManagerHome).
- The
create()
method in the stub communicates with the home object on
the J2EE server. Specifically, the create call is passed through the stub and skeleton
to the create() method in the home
object bank.ManagerBeanHomeImpl on the server
(see listing 1). This object then instantiates the EJB
object (`remote object') of class bank.ManagerBean_EJBObjectImpl and the communication
skeleton bank._ManagerBean_EJBObjectImpl_Tie
(lines 6 and 7 in listing 1).
- The home object calls
setContainer() on the new remote
object, so that the remote object can invoke container services later. It
will need to do this, for example, to create new bank.ManagerBean
instances.
- Note that although we now have a home object and a remote object on the
server, we don't yet have any actual
bank.ManagerBean instances, and the bean
is not ready to do any work. However, since stateless session beans can be
pooled, on subsequent invocations of this client there may be existing
instances of bank.ManagerBean in the pool.
- The home object returns to the client, via its skeleton and stub, a
reference to the newly-created EJB object. The home stub on the client
actually returns to the client an instance of a the remote stub; this stub
will handle future communication with the EJB object. In this example, the
remote stub is an instance of a CORBA stub that implements
bank.Manager. Its signature is shown below.
public class bank,_Manager_Stub
extends javax.rmi.CORBA.Stub implements bank.Manager
{
static java.lang.Class class$javax$ejb$EJBHome;
static java.lang.Class class$javax$ejb$RemoveException;
static java.lang.Class class$java$lang$String;
static java.lang.Class class$bank$BankException;
static java.lang.Class class$java$util$Collection;
static {};
public _Manager_Stub();
public java.lang.String _ids()[];
static java.lang.Class class$(java.lang.String);
public javax.ejb.EJBHome getEJBHome() throws java.rmi.RemoteException;
public javax.ejb.Handle getHandle() throws java.rmi.RemoteException;
public java.lang.Object getPrimaryKey() throws java.rmi.RemoteException;
public boolean isIdentical(javax.ejb.EJBObject) throws java.rmi.RemoteException;
public void remove() throws java.rmi.RemoteException, javax.ejb.RemoveException;
public java.util.Collection getAllCustomers() throws java.rmi.RemoteException;
// Other business methods omitted...
}
The client does not have to cast this reference, as it is defined
to be of the proper type (it was created by a call to
create() in the home stub, whose return type is
bank.Manager.
- Note that the remote stub contains implementations of the business methods
getAllCustomers() etc. Of course, these methods do not
implement the functionality of these methods, this is done by the
implementation class bank.ManagerBean on the server. The stub
simply passes method invocations through to the implementation via the
remote skeleton and the EJB object.
In summary, the chain of communication between the client and the EJB
home object can be shown as follows:
|
Client --> Home stub -|-> Home skeleton --> Home object
_ManagerHome_Stub | _ManagerBean_HomeImpl_Tie ManagerHomeImpl
|
(client host) | (server host)
- 5. Now that we have a remote reference to the bean, we can call any of its
business methods. In this example we are calling
getAllCustomers().
- In fact, the client calls this method on
the remote stub, which communicates with the remote skeleton
(
bank._ManagerBean_EJBObjectImpl_Tie which calls the
corresponding method
bank.ManagerBean_EJBObjectImpl.getAllCustomers() in the EJB
object on the server.
- The EJB object asks the container for an instance of
bank.ManagerBean. Specifically, the EJB object
creates an instance of com.sun.ejb.Invocation() and sets its
fields to indicate the method that will be invoked, and the EJB object
(itself) that is requesting it (lines 6-8, listing 1). This object is then
passed to the container (line 9).
- The container checks whether the client is authorized to call
the requested method. If not, it throws an exception.
- If the client is authorized, the container checks whether any
instances of
ManagerBean are available.
If this is the first usage of this bean, it is only at this point
that the container will
realize that there is no bank.ManagerBean
instance to pass the method call on to.
- The container creates a new
SessionContext
object for the new bean instance it is about to create.
This object will allow the bean
to determine security and transaction attributes, for example. In fact,
the container instantiates
com.sun.ejb.containers.SessionContextImpl, which implements
the SessionContext interface.
- The container creates a new instance of
bank.ManagerBean, by calling newInstance() on
its class. A reference to this new instance is held in a pool for future
invocations. Note that calling newInstance() causes the
bean's no-arg constructor to be called, but does not initialize it in any
other way.
- The container calls
setSessionContext() on the
new instance of bank.ManagerBean, passing in the instance of
com.sun.ejb.containers.SessionContextImpl created previously.
The bean may store this reference, or ignore it, at the discretion
of the developer. Of course, a good reason for storing it is that
its methods can be used to check the security roles of the client.
- The container begins a new transaction for the requested method
call, if the transaction attributes specify that a transaction is
required, and no transaction currently exists.
- The container returns the reference to the new bean instance
to the remote object. In fact, it simply initializes the
ejb instance variable in the Invocation instance
(line 10 in listing 2).
- The remote object calls the business method
getAllCustomers() in the bean instance (line 11).
- The bean generates a list of customers (perhaps a list of
bank.Customer instances) and returns it as a
Collection. This return is to the EJB object.
- The EJB object signals the container (line 12) that the
method call is complete. The container will commit or roll back the
transaction, if it initiated a transaction for this method call.
- The EJB object
returns the output from the business method
to the remote skeleton (line 13), which passes it over the network to the remote
stub, which unmarshalls the data and passes it back to the client.
- On the client, the remote reference to the bean (the variable `m')
will eventually go out of scope. At some point after this the garbage
collection system will finalize it and remove it. This causes the remote
stub to be finalized, and a finalization notification to be sent over the
network to the remote skeleton. This signals the EJB object, which signals
the container, which may decide to remove the EJB object and the bean
instance. But that's a another story.
Listing 1: EJB home implementation sample
This class is representative of the home implementation that is generated
automatically by the J2EE server when the EJB is deployed. Note that the
listing is very short; there is only one method that is specific to the
current EJB: create(). All the other work is handled by the
superclass com.sun.ejb.containers.EJBHomeImpl.
1 package bank;
2 public final class BankHomeImpl
extends com.sun.ejb.containers.EJBHomeImpl
implements ManagerHome
{
3 public TestHomeImpl(com.sun.ejb.Container c) throws java.rmi.RemoteException
{
4 super(c);
}
5 public Manager create()
throws java.rmi.RemoteException, javax.ejb.CreateException
{
6 ManagerBean_EJBObjectImpl ejbObject =
(ManagerBean_EJBObjectImpl) createEJBObject(); // method in superclass
7 return (Manager)ejbObject.getStub();
}
Listing 2: EJB object implementation sample
This listing is representative of the EJB object class that
is generated automatically by the J2EE server when the EJB
is deployed. This class handles all requests for business methods sent
from the client to the EJB, delegating them where appropriate to the
real bean, bank.ManagerBean. For clarity, I have
removed the exception handling code (which is quite extensive).
1 package bank;
2 import java.util.*;
3 public final class ManagerBean_EJBObjectImpl
extends com.sun.ejb.containers.StatelessSessionEJBObjectImpl
implements ManagerBean
{
4 public ManagerBean_EJBObjectImpl() throws java.rmi.RemoteException { }
5 public Collection getAllCustomers()
throws java.rmi.RemoteException
{
6 com.sun.ejb.Invocation i = new com.sun.ejb.Invocation();
7 i.ejbObject = this;
8 i.method = bank.Manager.class.getMethod("getAllCustomers", new java.lang.Class[] {});
9 this.getContainer().preInvoke(i);
10 bank.ManagerBean ejb = (bank.ManagerBean) i.ejb;
11 Collection c = ejb.getAllCustomers();
12 this.getContainer().postInvoke(i);
13 return c;
}
}
|