Enterprise JavaBeans were originally defined in terms of remote interfaces, such as the ones we’ve been discussing. The use of remote interfaces gave a nice, clean design: beans and bean clients did not need to worry about where other beans were located, because all bean references were treated as remote references. Beans always communicated with each other using Java RMI.
But in the real world, when two or more enterprise beans interact, they are usually co-located; that is, they are deployed in the same EJB container system and execute within the same Java Virtual Machine. In this case, RMI really isn’t necessary, and imposes overhead that we’d rather do without. Why treat all beans as remote objects if, in fact, they are often local? EJB 2.0 introduced the Local Client API to give developers control over whether beans should be accessed as remote objects, using RMI, or as local objects.
In EJB 2.0 and 2.1, session and entity beans can implement either remote or local component interfaces, or both. Any type of enterprise bean (entity, session, or message-driven) can become a co-located client of a session or entity bean; for example, a message-driven bean can call methods on co-located entity beans using its local component interfaces. The Local Client API is similar to the Remote Client API, but it is less complicated. The Local Client API is composed of two interfaces, the local and local home interfaces, which are similar to the remote and remote home interfaces.
The local interface, like the remote
interface, defines business methods that can be invoked by
other co-located beans (co-located clients). These business methods
must match the signatures of business methods defined in the bean
class. For example, the CabinLocal
interface is
the local interface defined for the Cabin EJB:
package com.titan.cabin; import javax.ejb.EJBException; public interface CabinLocal extends javax.ejb.EJBLocalObject { public String getName( ) throws EJBException; public void setName(String str) throws EJBException; public int getDeckLevel( ) throws EJBException; public void setDeckLevel(int level) throws EJBException; public int getShipId( ) throws EJBException; public void setShipId(int sp) throws EJBException; public int getBedCount( ) throws EJBException; public void setBedCount(int bc) throws EJBException; }
The CabinLocal
interface is basically the same as
the CabinRemote
interface we developed in Chapter 4, with a couple of key differences. Most
importantly, the CabinLocal
interface extends the
javax.ejb.EJBLocalObject
interface, rather than
EJBObject
, and its methods do not throw the
java.rmi.RemoteException
. Here’s
the definition of the EJBLocalObject
interface:
package javax.ejb; import javax.ejb.EJBException; import javax.ejb.RemoteException; public interface EJBLocalObject { public EJBLocalHome getEJBLocalHome( ) throws EJBException; public Object getPrimaryKey( ) throws EJBException; public boolean isIdentical(EJBLocalObject obj) throws EJBException; public void remove( ) throws RemoveException, throws EJBException; }
The methods in the
EJBLocalObject
interface should be familiar to you already. The
getEJBLocalHome( )
method returns a local home object;
getPrimaryKey( )
returns the primary key (entity beans
only); isIdentical( )
compares two local EJB objects; and
remove( )
removes the enterprise bean. These
methods work just like their corresponding methods in the
javax.ejb.EJBObject
interface.
It’s also important to notice the differences
between EJBLocalObject
and
EJBObject
. EJBLocalObject
does
not extend the java.rmi.Remote
interface, because
it is not a remote object. Nor does EJBLocalObject
define a getHandle( )
method; handles are not
relevant when the client and the enterprise bean are located in the
same EJB container system. The Handle
is a
serializable reference that makes it easier for a remote client to
obtain a reference to an enterprise bean from a remote node. Since
co-located beans are located in the same container system, not across
a network, the Handle
object is not necessary.
The EJBLocalObject
and the local interfaces that
extend it do not throw a java.rmi.RemoteException
,
which is no longer needed. Instead, the local interfaces and
EJBLocalObject
throw
EJBException
. This exception is thrown by the
container when some kind of system error occurs or when transaction
errors cause the bean instance to be discarded.
EJBException
is a subtype of the
java.lang.RuntimeException
and is therefore an
unchecked exception.
Unchecked
exceptions do not have to be declared in the
throws
clause of the local component interfaces
and do not require the client to explicitly handle them using
try
/catch
blocks. However, we
choose to declare the EJBException
in the method
signatures of the CabinLocal
interface in order to
communicate to the client application that this type of exception is
possible.
The local home interface, like the remote
home interface, defines life-cycle methods that can be invoked by
other beans located in the same container. The
life-cycle
methods of the local home interface include find, create, and remove
methods similar to those of the remote home interface.
Here’s the definition of
CabinHomeLocal
, the local home interface of the
Cabin EJB:
package com.titan.cabin; import javax.ejb.EJBException; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface CabinHomeLocal extends javax.ejb.EJBLocalHome { public CabinLocal create(Integer id) throws CreateException, EJBException; public CabinLocal findByPrimaryKey(Integer pk) throws FinderException, EJBException; }
The CabinHomeLocal
interface is similar to its
counterpart, CabinHomeRemote
, which we developed
in Chapter 4. However,
CabinHomeLocal
extends
javax.ejb.EJBLocalHome
and does not throw the
RemoteException
from its create and find methods.
You may also have noticed that the create( )
and
findByPrimaryKey( )
methods return an instance of
the CabinLocal
interface, not the remote interface
of the Cabin EJB. The create and find methods of local home
interfaces always return EJB objects that implement the enterprise
bean’s local interface.
Local interfaces must always extend the
EJBLocalHome
interface, which is much simpler than
its remote counterpart, EJBHome
:
package javax.ejb; import javax.ejb.RemoveException; import javax.ejb.EJBException; public interface EJBLocalHome { public void remove(Object primaryKey) throws RemoveException, EJBException; }
Unlike the EJBHome
, the
EJBLocalHome
does not provide
EJBMetaData
and HomeHandle
accessors. The EJBMetaData
object, which is
primarily used by visual development tools, is not needed for
co-located beans. In addition, the HomeHandle
is
not relevant to co-located client beans any more than the
Handle
was, because co-located beans do not need
special network references. The EJBLocalHome
does
define a remove( )
method that takes the primary
key as its argument; this method works the same as its corresponding
method in the remote EJBObject
interface.
When an enterprise bean uses local component interfaces, the interfaces must be declared in the XML deployment descriptor. Here are the changes we need to make to the deployment descriptor for the Cabin bean:
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar> <enterprise-beans> <entity> <ejb-name>CabinEJB</ejb-name> <home>com.titan.cabin.CabinHomeRemote</home> <remote>com.titan.cabin.CabinRemote</remote> <local-home>com.titan.cabin.CabinHomeLocal</local-home> <local>com.titan.cabin.CabinLocal</local> <ejb-class>com.titan.cabin.CabinBean</ejb-class>
In addition to adding the <local-home>
and
<local>
elements, the
<ejb-ref>
element is changed to an
<ejb-local-ref>
element, indicating that a
local EJB object is being used instead of a remote one:
<ejb-local-ref> <ejb-ref-name>ejb/CabinHomeLocal</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <local-home>com.titan.cabin.CabinHomeLocal</local-home> <local>com.titan.cabin.CabinLocal</local> </ejb-local-ref>
We can easily redesign the TravelAgent EJB developed in Chapter 4 so that it uses the Cabin EJB’s local component interfaces instead of the remote component interfaces:
public String [] listCabins(int shipID, int bedCount) {
try {
javax.naming.Context jndiContext = new InitialContext( );
CabinHomeLocal home = (CabinHomeLocal)
jndiContext.lookup("java:comp/env/ejb/CabinHomeLocal");
Vector vect = new Vector( );
for (int i = 1; ; i++) {
Integer pk = new Integer(i);
CabinLocal cabin;
try {
cabin = home.findByPrimaryKey(pk);
} catch(javax.ejb.FinderException fe) {
break;
}
// Check to see if the bed count and ship ID match.
if (cabin.getShipId( ) == shipID &&
cabin.getBedCount( ) == bedCount) {
String details =
i+","+cabin.getName( )+","+cabin.getDeckLevel( );
vect.addElement(details);
}
}
String [] list = new String[vect.size( )];
vect.copyInto(list);
return list;
} catch(NamingException
ne) {
throw new EJBException(ne);
}
}
Three small changes are needed. The most important change is using
local component interfaces for the Cabin EJB instead of remote
interfaces. We do not need to use the
PortableRemoteObject.narrow( )
method when
obtaining the Cabin EJB’s home object because we are
not accessing the home across the network; we are accessing the home
object from the same JVM, so there’s no problem with
a regular Java cast. Eliminating this method call makes the code much
easier to read. We also changed the
try
/catch
block to catch the
javax.naming.NamingException
rather than the
EJBException
thrown by the local component
interface methods. It is easier to allow those exceptions to
propagate directly to the container, where they can be handled
better. Chapter 15 covers exception handling in
detail. To deploy the examples in this section, see Exercise 5.3 in
the Workbook.
Entity and session beans can provide either local or remote component interfaces, or they may use both so that the bean is accessible from remote and local clients. Whenever we have enterprise beans accessing each other from within the same container system, we must seriously consider using local component interfaces, as their performance is likely to be better than that of remote component interfaces.
However, relying on the Local Client API eliminates the location transparency of enterprise bean references. In other words, if we provide only a local client API, we cannot move the bean to a different server. The Remote Client API allows us to move enterprise beans from one server to another without impacting the bean code.
The Local Client API also passes object arguments by reference from one bean to another, as illustrated in Figure 5-5. This means that an object passed from enterprise bean A to enterprise bean B is referenced by both beans, so if B changes its values, A will see those changes.
With the Remote Client API, objects’ arguments (parameters or return values) are always copied, so changes made to one copy are not reflected in the other (see Figure 5-1).
Passing by reference can create some pretty dangerous situations if the enterprise beans that share the object reference are not coded carefully. In most cases, it is best to pass immutable objects without copying them first.
Why is the Local Client
API needed at all? Wouldn’t it have been possible to
amend the specification of the Remote Client API to account for
co-located container optimizations, making those optimizations
standard, configurable attributes in the deployment descriptor? The
only problem with that solution is semantics. The remote interfaces
extend java.rmi.Remote
, and all subtypes of the
java.rmi.Remote
interface are required to throw
java.rmi.RemoteException
types from methods. It
may have been difficult for developers to distinguish between a
co-located EJB object and a remote EJB object, which is an important
distinction if one is passing objects by reference while the other
passes them by copy.
However, it can also be difficult for some EJB developers to use both the Remote and Local Client APIs correctly and effectively. With local component interfaces, we are locked into a single JVM, and we cannot move beans from one container to the next at will. The arguments for and against the local component interfaces both have their merits. Whether we agree with the need for the Local Client API or not, local interfaces are here to stay, and we must learn to use them appropriately.