The Local Client API

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

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

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.

Deployment Descriptor

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>

Using the Local Client API

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.

When to Use Local Component Interfaces

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.

Passing by reference with the Local Client API

Figure 5-5. Passing by reference with the Local Client API

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.

Are Local Component Interfaces Necessary?

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.225