EJB 2.0 introduces the concept of local component interfaces, which are intended to provide different semantics and a different execution context for enterprise beans that work together within the same EJB container system.
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. Co-located beans do not need to use the network to communicate. Since they are in the same JVM, they can communicate more directly by avoiding the overhead of Java RMI-IIOP. However, the EJB 1.1 specification required that even co-located beans utilize Java RMI-IIOP semantics for communication. This specification did not require that a network protocol be used, but it did require that Java RMI-IIOP types be used and that all objects be copied, rather then referenced, when passed as arguments to methods.
EJB 1.1 vendors, determined to squeeze every ounce of performance out of their servers, optimized co-located beans. The optimizations required that the EJB container interpose on invocations from one EJB to another but allowed vendors to avoid the overhead of the network; arguments and returned values were processed within the JVM by the container and not serialized over the network. However, arguments still had to be copied, rather then passed by reference, which slowed invocations down slightly. Many, if not most, vendors offered a proprietary switch that allowed deployers to turn off the copy semantics of co-located beans, so that objects passed from one enterprise bean to another in the same container system could be passed by reference rather then copied, which improved performance.
Optimizations of co-located beans that included switches for toggling argument copying eventually became so pervasive across vendors that Sun Microsystems decided to make this option part of the specification. This is why local component interfaces, which make up the Local Client API, were introduced.
Session and entity beans can choose to 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 in many respects 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 discussed earlier in this chapter.
While explaining the local and local home interfaces and how they are used, we will create these interfaces for the Cabin EJB we developed in Chapter 4.
The local interface, like the remote
interface, defines the enterprise bean’s
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 and its methods
do not throw the java.rmi.RemoteException
.
Local interfaces must extend the
javax.ejb.EJBLocalObject
interface, while remote
interfaces must extend the javax.ejb.EJBObject
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
EJBLocalObject
interface defines several methods
that should be familiar to you from the previous sections. The
getEJBLocalHome()
method returns a local home object; the
getPrimaryKey()
method returns the primary key (entity beans
only); the
isIdentical()
method compares two local EJB objects; and
the remove()
method removes the enterprise bean.
These methods work just like their corresponding methods in the
javax.ejb.EJBObject
interface. It is also
important to point out that the EJBLocalObject
,
unlike the EJBObject
, does not extend the
java.rmi.Remote
interface, because it is not a
remote object.
You may have noticed that the EJBLocalObject
,
unlike the EJBObject
, does not define a
getHandle()
method. EJB local interfaces do not
define a getHandle()
method because a
javax.ejb.Handle
is not relevant when the client
and enterprise beans 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 on a remote node on a network. 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
.
These interfaces are used for co-located beans in the same JVM, not
for accessing remote enterprise beans, so the
RemoteException--
which is used to report network
or partial failures of a remote system—is not relevant.
Instead, the local interfaces and EJBLocalObject
throw the
EJBException
. The EJBException
is
thrown from local interface methods automatically by the container
when some kind of container system error occurs or when a transaction
errors occurs that causes the bean instance to be discarded.
The 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 EJBExeption
in the methods
signatures of the CabinLocal
interface to better
communicate to the client application that this type of exception is
possible.
The local home interface, like the remote
home interface, defines the enterprise bean’s life-cycle
methods that can be invoked by other co-located beans (co-located
clients). The
life-cycle
methods of the local home interface include find, create, and remove
methods similar to those of the remote home interface. The
CabinHomeLocal
, the local home interface of the
Cabin EJB, is shown in the following listing:
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 very similar to
its counterpart, CabinHomeRemote
, which we
developed in Chapter 4. However, the
CabinHomeLocal
extends the
javax.ejb.EJBLocalHome
and does not throw the
RemoteException
from its create and find methods.
You may also have noticed that the type returned from the
create()
and findByPrimaryKey()
methods is the CabinLocal
interface, not the
remote interface of the Cabin EJB. The create and find methods of
local home interfaces will always return EJB objects that implement
the local interface of that enterprise bean.
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
, 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, they must be declared in the XML deployment descriptor. This is a trivial matter. The changes are highlighted below:
<!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); } }
The bold code text shows the three small changes that were needed.
The most important change is using local component interfaces for the
Cabin EJB instead of remote interfaces. In addition, we do not need
to use the PortableRemoteObject.narrow()
method
when obtaining the Cabin EJB’s home object from the JNDI ENC.
This is because we are not accessing the home across the network; we
are accessing it from within the same JVM, so we know that the client
is a Java client and that no network protocol is in use. The
elimination of this method makes the code much easier to look at. We
also changed the try
/catch
block so that it will catch the
javax.naming.NamingException
and not the
EJBException
thrown by any of the local component
interface methods. In this case it is easier to allow those exception
to propagate directly to the container, where they can be handled
better. Chapter 14 covers exception handling in
detail.
Please refer to Workbook Exercise 5.3, The Local Component Interfaces. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
Entity and session beans can use either local or remote component interfaces, or they may use both so that the bean is accessible from remote and local clients. Any time you are going to have enterprise beans accessing each other from within the same container system you should seriously consider using local component interfaces, as their performance is likely to be better than that of remote component interfaces. With the Local Client API, no network infrastructure is involved and there is no argument copying.
Because local clients are in the same JVM, there is no need for network comminations. When a co-located client bean invokes a method on a local EJB object, the container intervenes to apply transaction and security management, but then delegates the invocation directly to the target bean instance. No network. Very little overhead. The complete absence of a network can increase the speed of an RMI loop by a full magnitude, so it is very desirable. However, relying on the Local Client API eliminates the location transparency of enterprise bean references. In other words, you cannot move the bean to a different server on the network, because it must be co-located. With the Remote Client API, you can move enterprise beans from one server to another without impacting the bean code. In other words, the Remote Client API provides better location transparency: you are not dependent on the location of the enterprise bean in order to invoke its methods.
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. Chapter 16 discusses using immutable dependent objects, which are also good candidates for passing by reference. However, if you are going to pass an object that is not immutable, the object should be copied before it is passed. That’s the rule of thumb, unless the passed objects are not modified by any beans other than the enterprise beans from which they came.
Some vendors have argued that local component interfaces were never needed in the first place and only added more complexity and no real functionality to the EJB platform. This is a reasonable argument, considering that most EJB 1.1 vendors were already optimizing the Remote Client API for co-located beans. As we discussed earlier, these vendors did not use the network for co-located bean invocations and provided argument-copy switches.
So why was the Local Client API needed at all? An alternative would
have been 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
, which is
specifically intended for remote objects. In addition, 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 important if
one is passing objects by reference while the other passes them by
copy.
However, it is also going to be difficult for EJB developers to get used to local component interfaces and to use them effectively. With local component interfaces, you are locked into a single JVM, and you 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 you agree with the need for the Local Client API or not, local interfaces are here to stay, and you should learn to use them appropriately.