Tutorial: Getting Started Using RMI-IIOP


This tutorial shows you the steps to follow to create a distributed version of the classic "Hello World" program using Java Remote Method Invocation (RMI) over Internet Inter-ORB Protocol (IIOP). RMI-IIOP adds CORBA (Common Object Request Broker Architecture) capability to Java RMI, providing standards-based interoperability and connectivity to many other programming languages and platforms. RMI-IIOP enables distributed Web-enabled Java applications to transparently invoke operations on remote network services using the industry standard IIOP defined by the Object Management Group. Runtime components include a Java ORB for distributed computing using IIOP communication.

RMI-IIOP is for Java programmers who want to program to the RMI interfaces, but use IIOP as the underlying transport. RMI-IIOP provides interoperability with other CORBA objects implemented in various languages - but only if all the remote interfaces are originally defined as Java RMI interfaces. It is of particular interest to programmers using Enterprise JavaBeans (EJB), since the remote object model for EJBs is RMI-based.

Other options for creating distributed applications are:


Tutorial: The Hello World Application

The distributed Hello World example uses a client application to make a remote method call via IIOP to a server running on the host from which the client was downloaded. When the client runs, "Hello from MARS!" is displayed.

This tutorial is organized as follows:

  1. The steps to write the source files
  2. The steps to compile the example
  3. The steps to run the example

step icon Each step in the tutorial is indicated by this symbol.


Write or Download the Source Files

There are three tasks to complete in this section:

  1. Define the functions of the remote class as an interface written in the Java programming language
  2. Write the implementation class
  3. Write the server class
  4. Write a client program that uses the remote service
The source files used in this tutorial are:

Follow the steps below to create the source files or download and unzip HelloRMIIIOP.zip.

Define the functions of the remote class as an interface written in the Java programming language

In the Java programming language, a remote object is an instance of a class that implements a Remote interface. Your remote interface will declare each of the methods that you would like to call from other machines. Remote interfaces have the following characteristics:

For this example, create all of the source files in the same directory, for example, $HOME/mysrc/helloWorld.

step iconCreate the file HelloInterface.java. The following code is the interface definition for the remote interface, HelloInterface, which contains just one method, sayHello:


//HelloInterface.java
import java.rmi.Remote;

public interface HelloInterface extends java.rmi.Remote {
   public void sayHello( String from ) throws java.rmi.RemoteException;
}
Because remote method invocations can fail in very different ways from local method invocations (due to network-related communication problems and server problems), remote methods will report communication failures by throwing a java.rmi.RemoteException.

Write The Implementation Class

At a minimum, a remote object implementation class, HelloImpl.java must:

step iconCreate the file HelloImpl.java. The code for this file follows. An explanation of each of the preceding steps follows the source code:

//HelloImpl.java
import javax.rmi.PortableRemoteObject;

public class HelloImpl extends PortableRemoteObject implements HelloInterface {
   public HelloImpl() throws java.rmi.RemoteException {
       super();     // invoke rmi linking and remote object initialization
   }

   public void sayHello( String from ) throws java.rmi.RemoteException {
       System.out.println( "Hello from " + from + "!!" );
       System.out.flush();
   }
}

Implement a remote interface

In the Java programming language, when a class declares that it implements an interface, a contract is formed between the class and the compiler. By entering into this contract, the class is promising that it will provide method bodies, or definitions, for each of the method signatures declared in that interface. Interface methods are implicitly public and abstract, so if the implementation class doesn't fulfill its contract, it becomes by definition an abstract class, and the compiler will point out this fact if the class was not declared abstract.

The implementation class in this example is HelloImpl. The implementation class declares which remote interface(s) it is implementing. Here is the HelloImpl class declaration:

  public class HelloImpl extends PortableRemoteObject
    implements HelloInterface{
As a convenience, the implementation class can extend a remote class, which in this example is javax.rmi.PortableRemoteObject. By extending PortableRemoteObject, the HelloImpl class can be used to create a remote object that uses IIOP-based transport for communication.

Define the constructor for the remote object

The constructor for a remote class provides the same functionality as the constructor for a non-remote class: it initializes the variables of each newly created instance of the class, and returns an instance of the class to the program which called the constructor.

In addition, the remote object instance will need to be "exported". Exporting a remote object makes it available to accept incoming remote method requests, by listening for incoming calls to the remote object on an anonymous port. When you extend javax.rmi.PortableRemoteObject, your class will be exported automatically upon creation.

Because the object export could potentially throw a java.rmi.RemoteException, you must define a constructor that throws a RemoteException, even if the constructor does nothing else. If you forget the constructor, javac will produce the following error message:

        HelloImpl.java:3: unreported exception java.rmi.RemoteException; must be
        caught or declared to be thrown. 
        
        public class HelloImpl extends PortableRemoteObject implements HelloInterface{
               ^ 
        1 error
To review: The implementation class for a remote object needs to: Here is the constructor for the HelloImpl class:
  public HelloImpl() throws java.rmi.RemoteException { 
    super(); 
  }
Note the following:

Although the call to the superclass's no-argument constructor, super(), occurs by default (even if omitted), it is included in this example to make clear the fact that the superclass will be constructed before the class.

Provide an implementation for each remote method

The implementation class for a remote object contains the code that implements each of the remote methods specified in the remote interface. For example, here is the implementation for the sayHello() method, which returns the string "Hello from MARS!!" to the caller:
   public void sayHello( String from ) throws java.rmi.RemoteException {
       System.out.println( "Hello from " + from + "!!");
       System.out.flush();
   }

Arguments to, or return values from, remote methods can be any data type for the Java platform, including objects, as long as those objects implement the interface java.io.Serializable. Most of the core classes in java.lang and java.util implement the Serializable interface. In RMI:

Write The Server Class

A server class is the class which has a main method that creates an instance of the remote object implementation, and binds that instance to a name in the Naming Service. The class that contains this main method could be the implementation class itself, or another class entirely.

In this example, the main method is part of HelloServer.java, which does the following:

step iconCreate the file HelloServer.java. The source code for this file follows. An explanation of each of the preceding steps follows the source code:

//HelloServer.java
import javax.naming.InitialContext;
import javax.naming.Context;


public class HelloServer {
    public static void main(String[] args) {
        try {
            // Step 1: Instantiate the Hello servant
            HelloImpl helloRef = new HelloImpl();

            // Step 2: Publish the reference in the Naming Service 
            // using JNDI API
            Context initialNamingContext = new InitialContext();
            initialNamingContext.rebind("HelloService", helloRef );

            System.out.println("Hello Server: Ready...");

         } catch (Exception e) {
            System.out.println("Trouble: " + e);
            e.printStackTrace();
         } 
     }
}

Create an instance of a remote object

The main method of the server needs to create an instance of the remote object implementation, or Servant. For example:
    HelloImpl helloRef = new HelloImpl();
The constructor exports the remote object, which means that once created, the remote object is ready to accept incoming calls.

Publish the object reference

For a caller (client, peer, or client application) to be able to invoke a method on a remote object, that caller must first obtain a reference to the remote object.

Once a remote object is registered on the server, callers can look up the object by name (using a naming service), obtain a remote object reference, and then remotely invoke methods on the object. In this example, we use the Naming Service that is part of the Object Request Broker Daemon (orbd).

For example, the following code binds the name "HelloService" to a reference for the remote object:

            // Step 2: Publish the reference in the Naming Service 
            // using JNDI API
            Context initialNamingContext = new InitialContext();
            initialNamingContext.rebind("HelloService", helloRef );

Note the following about the arguments to the rebind method call:

Write a client program that uses the remote service

The client application in this example remotely invokes the sayHello method in order to get the string "Hello from MARS!!" to display when the client application runs.

step iconCreate the file HelloClient.java. Here is the source code for the client application:

//HelloClient.java
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
import javax.rmi.*;
import java.util.Vector;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import javax.naming.Context;

public class HelloClient {

    public static void  main( String args[] ) {
        Context ic;
        Object objref;
        HelloInterface hi;

        try {
            ic = new InitialContext();
         
        // STEP 1: Get the Object reference from the Name Service
        // using JNDI call.
            objref = ic.lookup("HelloService");
            System.out.println("Client: Obtained a ref. to Hello server.");

        // STEP 2: Narrow the object reference to the concrete type and
        // invoke the method.
            hi = (HelloInterface) PortableRemoteObject.narrow(
                objref, HelloInterface.class);
            hi.sayHello( " MARS " );

        } catch( Exception e ) {
            System.err.println( "Exception " + e + "Caught" );
            e.printStackTrace( );
            return;
        }
    }
}

First, the client application gets a reference to the remote object implementation (advertised as "HelloService") from the Naming Service using Java Naming and Directory Interface [TM] (JNDI) calls. Like the Naming.rebind method, the Naming.lookup method takes java.lang.String value representing the name of the object to look up. You supply Naming.lookup() the name of the object you want to look up, and it returns the object bound to that name. Naming.lookup() returns the stub for the remote implementation of the Hello interface to its caller (HelloClient).


Compile the Example

The source code for this example is now complete and the directory contains four files: In this section, you compile the remote object implementation file, HelloImpl.java, in order to create the .class files needed to run rmic. You then run the rmic compiler to create stubs and skeletons. A stub is a client-side proxy for a remote object which forwards RMI-IIOP calls to the server-side dispatcher, which in turn forwards the call to the actual remote object implementation. The last task is to compile the remaining .java source files to create .class files.

The following tasks will be completed in this section:

  1. Compile the remote object implementation
  2. Use rmic to generate stubs and skeletons
  3. Compile the source files

Compile the remote object implementation

To create stub and skeleton files, the rmic compiler must be run on the fully-qualified package names of compiled class files that contain remote object implementations. In this example, the file that contains the remote object implementations is HelloImpl.java. To generate the stubs and skeletons:

step iconCompile HelloImpl.java, as follows:

    javac -d . -classpath . HelloImpl.java

The "-d ." option indicates that the generated files should be placed in the directory from which you are running the compiler. The "-classpath ." option indicates that files on which HelloImpl.java is dependent can be found in this directory.

Use rmic to generate skeletons and stubs

To create CORBA-compatible stub and skeleton files, run the rmic compiler with the -iiop option. The rmic -iiop command takes one or more class names as an argument and produces class files of the form _HelloImpl_Tie.class and _HelloInterface_Stub.class. The remote implementation file, HelloImpl.class, is the class name to pass in this example.

For an explanation of rmic options, you can refer to the Solaris rmic  manual page or the Windows rmic manual page.

step iconTo create the stub and skeleton for the HelloImpl remote object implementation, run rmic like this:

    rmic -iiop HelloImpl

The preceding command creates the following files:

Compile the source files

step iconCompile the source files as follows:

    javac -d . -classpath . HelloInterface.java HelloServer.java HelloClient.java

This command creates the class files HelloInterface.class, HelloServer.class, and HelloClient.class. These are the remote interface, the server, and the client application respectively. For an explanation of javac options, you can refer to the Solaris javac manual page or the Windows javac manual page.


Run the Example

The following tasks will be completed in this section:
  1. Start the Naming Service
  2. Start the server
  3. Run the client application

Start the Naming Service

For this example, we will use the Object Request Broker Daemon, orbd, which includes both a Transient and a Persistent Naming Service, and is available with every download of J2SE 1.4 and higher.

For a caller (client, peer, or client application) to be able to invoke a method on a remote object, that caller must first obtain a reference to the remote object.

Once a remote object is registered on the server, callers can look up the object by name, obtain a remote object reference, and then remotely invoke methods on the object.

step iconStart the Naming Service by running orbd from the command line.

For this example, on the Solaris operating system:

    orbd -ORBInitialPort 1050&

or, on the Windows operating system:

    start orbd -ORBInitialPort 1050

You must specify a port on which to run orbd. For this example the port of 1050 is chosen because in the Solaris operating environment, a user must become root to start a process on a port under 1024. For more on the orbd tool, you can refer to the orbd manual page.

You must stop and restart the server any time you modify a remote interface or use modified/additional remote interfaces in a remote object implementation. Otherwise, the type of the object reference bound in the Naming Service will not match the modified class.

Start the server

Open another terminal window and change to the directory containing the source files for this example. The command for running the client has been spread out below to make it easier to read, but should be typed without returns between the lines. The following command shows how to start the HelloServer server. If you used a port other than 1050 or a host other than localhost when starting the orbd tool, replace those values in the command below with the actual values used to start orbd.

step iconStart the Hello server, as follows:

    java 
      -classpath . 
      -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
      -Djava.naming.provider.url=iiop://localhost:1050 
      HelloServer

For an explanation of java options, you can refer to the Solaris java manual page or the Windows java manual page.

The output should look like this:

Hello Server: Ready ...

Run the client application

Once the Naming Service and server are running, the client application can be run. From a new terminal window, go to the source code directory, and run the client application from the command line, as shown below. The command for running the client has been spread out below to make it easier to read, but should be typed without returns between the lines. If you used a port other than 1050 or a host other than localhost when starting the orbd tool, replace those values in the command below with the actual values used to start orbd.

step iconStart the client application, as follows:

    java 
      -classpath . 
      -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
      -Djava.naming.provider.url=iiop://localhost:1050 
      HelloClient 
After running the client application, you will see output similar to the following on your display in the client window:
Client: Obtained a ref. to Hello server.

The following message will display in the server window:

Hello from  MARS 

ORBD and the Hello server will continue to run until they are explicitly stopped. On Solaris, you can stop these processes using the pkill orbd and pkill HelloServer commands from a terminal window. On Windows, you can type Ctrl+C in a prompt window to kill the process.

This completes the basic RMI-IIOP tutorial. If you are ready to move on to more complicated applications, here are some sources that may help:


Copyright © 1993, 2014, Oracle and/or its affiliates. All rights reserved.