Chapter 12. Distributed Processing Using RMI

Understanding Distributed Processing

Enterprise applications are, by definition, logically distributed. The term distributed processing refers to an application that is made up of multiple processes working together. The processes may be running on separate hosts on a network or on the same host. In a pure Java implementation, Distributed Processing refers to classes running in different Java Virtual Machines (JVMs) working in cooperation. This is where Remote Method Invocation (RMI) comes into play. RMI provides the mechanism for a Java class to call methods on an object running in a separate Java Virtual Machine. This is actually the underlying core technology for Enterprise Java Beans (EJBs). At the heart of a J2EE application is an EJB that implements the business logic for the application. Enterprise Java Beans provide a remote interface that acts as their thin client to the bean. Typically, a servlet uses the remote interface to communicate with the EJB. The details of the RMI structure and how it is used with J2EE are covered in this chapter. It is important for J2EE developers to understand these details to be able to build reliable, high-performance J2EE applications.

The Java 2 Standard Edition comes with all the classes, interfaces, and tools necessary to create distributed applications that use RMI. By using them, you can learn the technology and develop working prototypes or applications with moderate performance requirements. The performance and reliability requirements of J2EE applications go beyond the basic capabilities of the standard edition RMI.

The WebLogic RMI provides enhanced capabilities that address your needs as a J2EE developer. This chapter covers the standard edition RMI, as well as the enhancements offered by the WebLogic RMI.

From this background knowledge of the underlying architecture, you will have a much better understanding of the roles played by each component in a distributed application that uses RMI. The communication between the processes is ultimately a data stream over a TCP/IP socket. The socket provides the communication path for the client/server protocol. The server listens to a specific TCP/IP port and accepts connections on that port. The client connects to the server by specifying the hostname and port number of the socket server. When the socket is connected, it acts as a bidirectional pipe. Any data written by the client is read by the server, and vice versa. Following these requirements, RMI provides a server, client, and data stream that is passed across the socket. Figure 12.1 shows a graphical representation of the RMI architecture. The server is the RMI remote object, and the client is the application that uses the remote interface. The data stream is automatically generated for you through the process of “marshalling” the arguments and return values of the remote method calls. The process of converting the parameters and return values of a remote method call into a data stream is referred to as marshalling. This process will be described in detail in the “RMI Design Model” section later in this chapter.

The RMI client and server communicate through a layered architecture over a TCP/IP network connection.

Figure 12.1. The RMI client and server communicate through a layered architecture over a TCP/IP network connection.

The RMI Design Model

The structure of a distributed application that uses RMI follows a common design pattern. RMI provides the technology to create a thin client that communicates with the more complex remote object. This is identical to the “Proxy” structural design pattern which is a placeholder class for a more complex class. Please refer to Chapter 6, “Transitioning from Software Design to J2EE Technology Components and Services” for a complete description of the “Gang-of-Four” Design Patterns. A remote interface defines the API, a remote object implements the interface, and a client uses the interface, as shown in Figure 12.2.

The RMI design model consists of a client that uses a remote interface and a RMI server that implements the interface.

Figure 12.2. The RMI design model consists of a client that uses a remote interface and a RMI server that implements the interface.

These three components are written by the application programmer. This leaves two questions: How does the client find the remote object, and how is the remote method actually “called” across the network? As you may have suspected, to discover the location of the remote object, you use a naming service—in this case, the RMI registry. The RMI registry is a server process that performs the naming service. RMI servers register their remote object and the clients retrieve the remote object through the RMI registry. The registry process is started by running the command rmiregistry. The API for the RMI registry is nearly identical to the naming context for JNDI. Chapter 14, “Locating Named Services Through JNDI,” is devoted to the Java Naming and Directory Interface (JNDI). There, you will find a comparison between the RMI registry and JNDI. In a J2EE application, JNDI can be used to completely replace the RMI registry.

The remote method call is performed through two generated classes called the stub and the skeleton. These classes marshal the parameters and return value across the network connection. Again, the term marshal means to serialize the parameters so they can be written to the remote object that, in turn, deserializes them back into objects. The stub and skeleton were shown in Figure 12.1 as the layer in the RMI architecture which the client and server directly communicate. The return value is also processed by marshalling in the same manner. This concept is built into the Java language through the Serializable interface. Therefore, the only restriction with remote methods is that all the parameters and the return value must be serializable. The client and remote object do not need to know anything about the stub or skeleton. As far as the client knows, it is calling the method directly.

The descriptions of the remote interface, remote object, and RMI client application in the following sections show that they are implemented as normal Java interfaces and classes. The standard rules for Java packages and access modifiers apply. By following these rules, you can implement the remote object with public or package scope. You need to pay particular attention to the Java RMI interfaces and classes that are extended and used by your implementation.

RMI Package Names

The classes and interfaces that compose the API for RMI, shown in Table 12.1, are released in packages that organize common functionality. All packages from the JavaSoft implementation begin with java.rmi. The corresponding package for the WebLogic implementation begins with weblogic.rmi. WebLogic offers features and enhancements that are discussed in this chapter. However, for the most part, only the import statements need to be changed when a JavaSoft RMI implementation is being migrated to a WebLogic implementation.

Table 12.1. The Classes and Interfaces for RMI Are Organized into Five Java Packages

JavaSoft

WebLogic

Comment

java.rmi

weblogic.rmi

core API

java.rmi.registry

weblogic.rmi.registry

API for naming service

java.rmi.server

weblogic.rmi.server

API for server-side operations

java.rmi.activation

weblogic.rmi.activation

API for activatable objects

java.rmi.dgc

weblogic.rmi.dgc

API for distributed garbage collection

Creating an RMI Application

Referring to Figure 12.2, the basic structure of an RMI application consists of a remote interface, a remote object, and a client. The remote interface exposes the public API of the remote object. The remote object implements the remote interface and the client uses the remote interface. Therefore, the steps to create an RMI application are:

  1. Write the Remote Interface.

  2. Write the Server.

  3. Compile the Remote Interface and Server.

  4. Generate the stubs and skeletons for the Server.

  5. Write the Client.

  6. Compile the Client.

RMI Registry Naming Service

The RMI registry is the naming service used by RMI clients and servers. The purpose of a naming service is to bind a name to an object. Binding a name enables you to look up the name to retrieve a reference to the object. The server binds a name to the remote object when registering. The client looks up the name to retrieve a reference to the registered object. WebLogic programmers have access to two RMI naming services: the default Java 2 Standard Edition, J2SE, RMI registry and the WebLogic RMI registry. The WebLogic naming service is a drop-in replacement for the J2SE registry and therefore provides the same API. Only the import statements need to be changed. A full description of both naming services is provided in the following sections.

RMI Registry API

The API used by the client and server is provided by static methods in the Naming class. The methods are bind(), rebind(), unbind(), and lookup() which all pass the registered name as a URL in the first parameter. The format of the URL is

//host:port/Name

If the well-known port 1099 is being used, you can leave off the :port field. If the host is localhost and the port is 1099, you need only specify the Name field. Some examples are

//localhost:1099/MyObjectName
//myhostname/MyObjectName
//myhostname:2002/MyObjectName
MyObjectName

The RMI clients and servers communicate with the RMI registry through static methods in the Naming class. The remote object uses the bind() method to register a name with the remote object. The name may already be in use, which causes the AlreadyBoundException to be thrown. If you receive this message, you have three choices:

  • Bind a different name

  • Use the rebind() method to force the binding

  • Handle it as an error condition

The RMI client uses the Naming.lookup() method, passing the URL string as a parameter. The return value is an object that must be downcast to the remote interface that you were expecting to receive. The lookup() method uses the following three exceptions to indicate error conditions:

  • java.net.MalformedURLException—. The URL string is not syntactically correct.

  • java.rmi.NotBoundException—. The name is not bound to a remote object.

  • java.rmi.RemoteException—. The requested remote object is not available.

A complete RMI example is provided at the end of this chapter. Listing 12.1 is a snipet of code showing the usage of the Naming.lookup() method:

Example 12.1. Example RMI lookup()

   try {
        // locate the remote object in the rmiregistry
        IHello hello = (IHello)Naming.lookup( "Hello" );

        // use the remote object
        System.out.println( hello.getGreeting() );
    }
    catch( MalformedURLException x ) {
        System.err.println( "URL is invalid:" + x.getMessage() );
    }
    catch( NotBoundException x ) {
        System.err.println( "Name is not bound" + x.getMessage() );
    }
    catch( RemoteException x ) {
        System.err.println( "Remote object is not available" + x.getMessage() );
    }
}

Java 2 Standard Edition RMI Registry

The naming service executable for the J2SE RMI is rmiregistry. The RMI registry must be running for RMI clients to look up the remote interface that they want to use. The rmiregistry can be launched in two ways: either as a standalone process or within the remote object.

To run rmiregistry in the background on a Unix system, type

rmiregistry &

To run rmiregistry from a Windows command prompt, type

start /min rmiregistry

This command starts rmiregistry using the default port 1099. The command-line argument allows you to set the port to a different number. For example, if you want to run rmiregistry on port 2002, type the following:

rmiregistry 2002 &

As a convenience, the remote object can create an rmiregistry within its own process space by using the java.rmi.registry.LocateRegistry.createRegistry() method. The parameter is a port number. Again, the well-known port for rmiregistry is 1099. You can use 1099 or any other port that is available. This method throws RemoteException if an rmiregistry is already running on the given port. In most cases, this error is not fatal. Simply use the rmiregistry that is running. Refer to the sample RMI application shown later in this chapter in Listings 12.2 through 12.4 for sample code of a remote object and an RMI client. The code snippet to programmatically start the RMI registry is shown in Listing 12.2.

Example 12.2. The RMI Registry Can Be Started Programmatically by the RMI Server

        // start the rmiregistry on the default port
        // check if rmiregistry is already running
        try
        {
            LocateRegistry.createRegistry(1099);
        }
        catch( RemoteException x )
        {
            System.out.println(
                "Using rmiregistry that is already running" );
}

WebLogic RMI Registry

WebLogic RMI registry replaces the standard RMI registry process. WebLogic RMI runs inside the WebLogic Server. No additional processes are necessary. The WebLogic RMI registry supports a protocol field in the bound URL. In addition to the standard rmi:// client protocol, http:// and https:// can be used as well.

The WebLogic RMI registry is integrated with the WebLogic JNDI. You can therefore bypass the RMI registry and use JNDI directly. This allows you to register the RMI services with the enterprise directory in the same manner that Enterprise Java Beans are registered.

The Remote Interface

The remote interface is the starting point for creating an RMI application. It is written as a standard Java interface. The following rules must be followed whether J2SE or WebLogic is being used for development:

  • The remote interface must extend the Remote class.

  • All parameters must be Serializable.

  • All return values must be Serializable.

In addition to these common requirements, J2SE and WebLogic have individual requirements or enhancements that must be followed. To maximize portability, follow the J2SE requirements. The WebLogic implementation is compatible with J2SE and only the import statement would need to be changed.

Java 2 Standard Edition Remote Interface

For a J2SE implementation, the remote interface must extend the java.rmi.Remote interface to ensure that this new interface can be cast to a Remote interface. All methods defined by the remote interface must be declared to throw java.rmi.RemoteException. The IHitCounter in the sample RMI application shown later in this chapter is a remote interface.

WebLogic Remote Interface

The WebLogic remote interface provides ease-of-use extensions for remote interfaces and code generation. For a WebLogic implementation, the remote interface must extend weblogic.rmi.Remote. Each method in the interface does not need to declare a java.rmi.RemoteException in its throws block. This allows more freedom for your application to throw specific exceptions. The exceptions can extend Exception or RuntimeException depending upon the needs of the application.

Remote Object

The remote object is the active instance of the remote server class. The remote object must implement the remote interface. Specific requirements on the implementation are only required by J2SE. A WebLogic implementation does not require extending the UnicastRemoteObject base class and does not require each method to throw RemoteException.

Java 2 Standard Edition Remote Object

The remote object provides the implementation for the remotely accessible object. A J2SE implementation must extend the java.rmi.server.UnicastRemoteObject. It must also implement the remote interface that was created for it. The following is a summary of the rules that apply to a J2SE remote object:

  • It extends java.rmi.server.UnicastRemoteObject.

  • It implements the remote interface that was created for it.

  • The explicitly written default constructor throws java.rmi.RemoteException, even if the constructor does nothing else. This is true because the base class super() constructor, which is automatically called, throws RemoteException.

  • The main() method must construct the remote object and register it with the rmiregistry using Naming.bind() or Naming.rebind().

  • The main() method must install a security manager if the remote object is bound to a network host. You should not install a security manager if localhost is being used. To install the RMISecurityManager, add the following code to the beginning of the main() method:

    System.setSecurityManager( new RMISecurityManager() );
    

The JVM that is serving the registered remote object will continue to run after the main() method returns. It runs because the remote object is being served by another thread, allowing RMI clients continued access to the running remote object as long as the Java process that loaded the remote object is running.

WebLogic Remote Object

In WebLogic, there is no requirement to extend UnicastRemoteObject. As a result, you can utilize a logical object hierarchy. In addition, each method in the interface does not need to declare a java.rmi.RemoteException in its throws block. Exceptions that your application throws can be specific to that application and can extend RuntimeException. Finally, no Security Manager is required. All WebLogic RMI services are provided by the WebLogic Server, which provides more sophisticated security options, such as Secure Sockets Layer (SSL) and Access Control Lists (ACLs). You can remove the call to setSecurityManager() when converting RMI code to WebLogic RMI.

RMI Client Application

The RMI client really benefits from all the work that is being done for it. All it has to do is look up the remote interface in the RMI registry and use that interface. The parameter to Naming.lookup() is a URL in the format noted previously. The return value is a Remote object that must be downcast to the remote interface that you are expecting to match the given name. The remote methods are called exactly the same as any other object method. The only condition that must be dealt with is handling exceptions. The exceptions thrown by Naming.lookup() were listed in the “RMI Registry Naming Service” section. Remember, all remote methods in a J2SE implementation throw RemoteException and therefore must be called from a try block that catches RemoteException. The only other point to note is that you may need to import the remote interface if it is in a different package from the client. The HitCounterClient, in Listing 12.5, shows an example of an RMI client application.

Generating Stubs and Skeletons Using rmic

As shown in Figure 12.1, the RMI architecture uses stubs and skeletons as the layer directly below the client and server, respectively. The stubs and skeletons are automatically generated by the RMI compiler, rmic. After the remote interface and remote object are compiled, you must generate the stub and skeleton classes. These classes perform the communication and data marshalling between the client and remote object. The stub is used by the client, and the skeleton is used by the remote object. These classes are generated by the rmic. The command-line argument to rmic is the full package and class name of the compiled RMI server. The RMI compiler uses the same command-line argument as javac to create directories for the package name. If you are not using package names, you do not need to use the -d option. The stub and skeleton are automatically placed in the same package as the remote object implementation class. The stub and skeleton must be in the CLASSPATH for the client and the remote object.

The RMI compiler from JavaSoft is a standalone executable. The RMI compiler from Weblogic is a Java class that is run using Java. Both perform the same function, to create the stubs and skeletons.

Java 2 Standard Edition rmic

The RMI compiler released with J2SE is the rmic executable. The command line is the class name of the RMI server. For example, if the package name is com.mycompany.j2ee and the implementation class for the remote object is MyRmiImpl, the rmic command is

rmic -d . com.mycompany.j2ee.MyRmiImpl

The classes that the rmic will generate are the two compiled class files:

commycompanyj2eeMyRmiImpl_Stub.class
commycompanyj2eeMyRmiImpl_Skel.class

WebLogic rmic

The WebLogic RMI compiler is a Java class named weblogic.rmic. Like the JavaSoft rmic, the full package and class name of the compiled RMI server is taken as a command-line argument. The -d command-line argument is also used to build the directory structure for the package name of the stubs and skeletons. Using the same example RMI server from the previous section, the command line for the WebLogic RMI compiler is:

java weblogic.rmic -d . com.mycompany.j2ee.MyRmiImpl

The classes that the rmic will generate are the two compiled class files:

commycompanyj2eeMyRmiImpl_WLStub.class
commycompanyj2eeMyRmiImpl_WLSkel.class

The WebLogic RMI compiler has a keepgenerated command-line option that will save the Java source files for the generated stub and skeleton for your viewing pleasure.

Sample RMI Application

The sample RMI application you create in this section is a hit counter that keeps track of the number of times the remote object was called. It is compliant with the Java 2 Standard Edition RMI. The remote interface has only one method, getHitCount(). The remote interface is called IHitCounter, the remote object implementation is called HitCounter, and the RMI client is called HitCounterClient. Notice how the main() method in HitCounter registers the remote object with the rmiregistry and also how the HitCounterClient uses the Naming.lookup() method to locate the remote object.

Creating a Java RMI application is a fairly straightforward process. The steps to create and execute the sample RMI application are as follows:

  1. Write the IHitCounter.java remote interface (see Listing 12.3).

  2. Write the HitCounter.java remote object implementation (see Listing 12.4).

  3. Write the HitCounterClient.java RMI client application (see Listing 12.5).

  4. Compile IHitCounter.java, HitCounter.java, and HitCounterClient.java using javac.

  5. Run rmic HitCounter to create HitCounter_Stub.class and HitCounter_Skel.class.

  6. Optionally, you can run rmiregistry. You can skip this step if you want because HitCounter will attempt to create a registry just in case it is not running.

  7. On a Unix shell, run

    java HitCounter &
    

    to run the remote object in the background. From a Windows command prompt, run

    start /min java HitCounter
    
  8. Run java HitCounterClient multiple times to see the hit count increase.

The remote interface defines the methods that are provided by the RMI server to the RMI client. Listing 12.3 shows the IHitCounter sample remote interface that defines the getHitCount() method.

Note

The methods in the remote interface are not required to throw RemoteException when WebLogic RMI is used.

Example 12.3. IHitCounter Remote Interface

/**
    RMI Remote Interface for HitCounter
*/

// Imports
import java.rmi.*;

public interface IHitCounter extends Remote
{
    // return number of times this method was called
    public int getHitCount() throws RemoteException;
}

The remote object provides the implementation for the remote interface. Listing 12.4 shows the HitCounter remote object that implements the IHitCounter remote interface. This example increments a hit counter and returns the value each time the getHitCount() method is called. The main() in HitCounter attempts to launch the RMI registry. If an exception is thrown, it is assumed that the RMI registry is already running and will use the running instance. The primary purpose of the main() is to bind the HitCounter remote object to the name “HitCounter” in the RMI registry.

Example 12.4. HitCounter Remote Object

/**
    RMI Remote Object HitCounter
*/

// Imports
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.net.*;

public class HitCounter
    extends  UnicastRemoteObject
    implements IHitCounter
{
    // object attributes
    private int hitCount;

    /**
        Default constructor
        @throws RemoteException
    */
    public HitCounter() throws RemoteException
    {
        hitCount = 0;
    }

    /**
        getHitCount
        @returns hitCount for this RMI Server
        @throws RemoteException
    */
    public int getHitCount()
        throws RemoteException
    {
        // increment hitCount
        hitCount++;

        return hitCount;
    }

    /**
        main method
        @param args command line arguments
    */
    public static void main( String[] args )
    {
        // start the rmiregistry on the default port
        // check if rmiregistry is already running
        try
        {
            LocateRegistry.createRegistry(1099);
        }
        catch( RemoteException x )
        {
            System.out.println(
                "Using rmiregistry that is already running" );
        }

        try
        {
            // create the remote object
            HitCounter hc = new HitCounter();

            // register remote object with rmiregistry
            Naming.bind( "HitCounter",	hc );
        }
        catch( MalformedURLException x )
        {
            System.err.println( x.toString() ) ;
            System.exit( -1 );
        }
        catch( AlreadyBoundException x )
        {
            System.err.println( x.toString() );
            System.exit( -1 );
        }
        catch( RemoteException x )
        {
            System.err.println( x.toString() );
            System.exit( -1 );
        }

        System.out.println( "serving HitCounter" );
    }
}

The HitCounterClient, shown in Listing 12.5 performs a lookup() operation on the RMI registry to find the HitCounter RMI server. The lookup() method returns an instance that implements the IHitCounter interface. The client calls methods defined by the IHitCounter interface to communicate with the HitCounter RMI server.

Example 12.5. HitCounterClient

/**
    Client of RMI Remote Object HitCounter
*/

// Imports
import java.rmi.*;

public class HitCounterClient
{
    /**
        main method
        @throws Exception
    */
    public static void main( String[] args )
        throws Exception
    {
        // locate the remote object in the rmiregistry
        IHitCounter ihc = (IHitCounter)Naming.lookup( "HitCounter" );

        // use the remote object
        System.out.println( "count = " + ihc.getHitCount() );
    }
}

WebLogic Optimizations for RMI

The BEA WebLogic Application Server has implemented optimizations for RMI to greatly increase performance. As mentioned in the first section of this chapter, communication with Enterprise Java Beans is performed with RMI. The optimizations implemented by WebLogic are focused on increasing performance when the client and EJBs are running on the same WebLogic Application Server. It is actually very common to have the servlet call an EJB that is running on the same server. When the application server identifies the collocation of the client and implementation object, it skips the RMI infrastructure and calls the methods directly. This technique is known as collocation optimization. Avoiding the marshaling of data has dramatic impact on increasing performance.

Under standard operation, when a remote method is called, each of the parameters are serialized, written across the socket, read by the server, and deserialized. This is extremely time consuming. The WebLogic collocation optimizations avoid this by calling the method directly when the client and server are hosted by the same application server.

WebLogic RMI over IIOP

A significant enhancement of WebLogic RMI is support for use over Internet Inter-ORB Protocol (IIOP). This support exposes the RMI remote interface to CORBA clients. CORBA, which stands for Common Object Request Broker Architecture was developed by the Object Management Group (OMG). Please refer to the http://www.omg.org web page for more information on CORBA. The CORBA clients and servers can be implemented in a variety of languages including C++ and Java. CORBA objects communicate using the IIOP. By providing the capability to run RMI over IIOP, CORBA clients written in C++ can invoke methods on Java remote objects. This enhancement to the WebLogic application server solves a number of business needs. WebLogic provides the platform to migrate from CORBA to J2EE, as well as allowing continued support for legacy clients written using CORBA. Recall that the underlying architecture of EJB is RMI. WebLogic RMI over IIOP therefore enables CORBA clients to use Enterprise Java Beans directly. This is accomplished through a Java-to-IDL mapping. The remote interface for CORBA is defined by a platform-neutral language called the Interface Definition Language (IDL). The IDL is compiled into stubs and skeletons that are used by the client and server, respectively. As in RMI, the stubs and skeletons are responsible for marshaling the parameters and return values of the remote method calls. The Java-to-IDL mapping defines how the IDL is derived from an RMI remote interface. The WebLogic RMI compiler uses the -idl command-line option to produce an IDL equivalent of the remote interface. The resulting IDL is compiled with an IDL compiler to generate the classes used by the CORBA client. The WebLogic server implements a CosNaming service that parses IIOP requests and dispatches them into the RMI runtime.

The WebLogic implementation of RMI-IIOP now includes the following features, as well as numerous performance enhancements:

  • Support for Object-Transaction-Services 1.2 (OTS 1.2), which adds transactional support to RMI-IIOP. It can be used to invoke remote objects hosted in foreign application servers.

  • CSIv2 (Common Secure Interoperability, version 2) provides client authentication, delegation, and privilege functionality; it also works with foreign application servers.

  • A WebLogic IIOP client that is fully able to cluster. You simply use the following command-line parameter with weblogic.rmic to enable support for the WebLogic IIOP client:

    -Dweblogic.system.iiop.enableClient=true
    
  • Hot code generation of IIOP stubs. You no longer need to use the -iiop flag with ejbc or rmic.

  • Support for the entire CosNaming API.

Summary

The primary value of writing distributed applications using RMI is to centrally locate the business logic and provide access through thin clients. This model is especially important with enterprise applications. This is why RMI is the enabling technology for Enterprise Java Beans. The servlet or applet needs only the remote interface to communicate with the remote object, which is the EJB. This greatly simplifies the clients and allows them to operate using far fewer resources. Vendors of application servers have implemented extensions to RMI. For example, the WebLogic RMI over IIOP enables CORBA clients’ access to Enterprise Java Beans.

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

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