Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

RMI, Passing and Returning Remote and Ordinary Objects

Java Programming, Lecture Notes 603, Revised 8/22/99.


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on October 10, 1998, using the JDK 1.1.6 download package (and sometimes the MS SDK). The purpose of this lesson is to explain some of the issues involved in passing and returning objects with RMI.

Introduction

For purposes of the discussion in this lesson, a remote object is an object of a class that extends the Remote interface either directly or indirectly.  An ordinary object is an object of a class that does not extend the Remote interface.

Previous lessons have given you an introduction to RMI.  I am going to assume that you have read those lessons and skip almost all of the normal introductory and overview material.

The material in this lesson is somewhat more complex than material in the previous RMI lessons in this series, so I'm going to give you the bottom line at the top, and then discuss a sample program that supports the bottom line.

Overview

When we teach courses in programming fundamentals using C++ or Pascal, we spend a lot of time introducing students to the concept of passing parameters and returning results by value and by reference.  Passing a parameter by value means that the function or method receives a copy of the original.  Modifying the copy has no effect on the original.

Passing by reference means that the method receives a pointer, reference, or handle to the original (depending on the jargon of the particular language involved.).  If that reference is used to make a change to the parameter, the original will be modified.

This is a concept that can be quite confusing to some students, and generally in Pascal, any variable can be passed either by value or by reference.  In C++ any variable or any object can be passed either by value or by reference.

Similar considerations apply when returning results.  In some cases, it is possible to return a result either by value or by reference.

Normally, the situation is somewhat less confusing in Java.  In Java, all primitive variables are passed by value.  All objects are passed by reference.  Therefore, if you modify an incoming primitive variable inside a method, that action has no effect on the original.  On the other hand, if you use an incoming reference to modify the object that it references, that action will modify the original.

However, with RMI, life is not quite so simple in Java either.  If a method that is invoked on a remote object returns an  ordinary object (see earlier definition of remote and ordinary objects) a copy of the object is serialized and sent to the invoking method on the client.  In effect, the object is returned by value.  If the method on the client modifies the object, that modification has no effect on the original object on the server.

On the other hand, if a method that is invoked on a remote object returns a remote object, a serialized copy of the object is not returned.  Rather, a stub is returned that represents the object to the client method.  This stub is equivalent to a reference to the object.  If the stub is used to modify the object, the original object on the server will be modified.

In a more general sense, during RMI, if ordinary objects are sent in either direction, a serialized copy of the object is actually sent.

If remote objects are sent in either direction, stubs representing those objects are sent in place of a serialized copy of the object, and the receiving code has access to the original object by using the stub as a reference.

Therefore, you must be more careful in your thinking when invoking remote methods under RMI than might normally be the case.  You may think that you are modifying an object when you are simply modifying a copy of the object.  Similarly, you may think that you are modifying a copy of an object when in fact, you are modifying the original object.

Sample Program

A remote object is defined that contains two instance variables.

One of the instance variables is an embedded remote object.  It is an instance of a class that implements the Remote interface.  This instance variable, being an object itself, has one instance variable of type int along with a pair of set and get methods which allow the value of the instance variable to be modified and retrieved.

The other instance variable in the main remote object is an object of type Date.  The Date class does not implement the Remote interface.  Therefore, this is an ordinary instance variable.

(Note that this sample program makes use of several deprecated methods of the Date class, but the class and the methods were ideal for demonstrating my points.  Just ignore the deprecated warnings when you compile the program.)

The Date class has a set method that makes it possible to set the number of the day of the month stored in an object of that class. This is in the same sense that the remote instance variable has a set method that makes it possible to set the int value stored in that object.

This program has six source files as described below:

The Batch file

The name of the batch file is Rmi05.bat.  By now you should be completely familiar with how my batch files work to control the RMI process so I won't discuss them here.  You can view a complete listing of the batch file near the end of this lesson.

The Interface Files

Two remote objects of different types are instantiated in this program, but only one of them is exposed to the client by way of the registry.  I will discuss that situation in more detail later.  Because there are two different types of remote objects, two different interface files are required.

The following fragment shows the interface definition for the main remote object.  I have snipped out the material that is not germane to this discussion.

The first method declaration is for a method to get the date object that is an instance variable of the main remote object.  This method returns an object of type Date.  There are no surprises here.

The next method declaration, named getRemObj()  is a little more complex.  In truth, this method returns an object of type Rmi05RemoteObjB.  However, if you write it that way, your program won't run successfully.  In order to cause everything to come together and run properly under RMI, you must declare this method as returning Rmi05RemoteIntfcB which is the name of the interface implemented by the class Rmi05RemoteObjB.

While the explanation for this behavior is fairly complex, this is generally because the stub that is sent to the client to represent this object only knows about the Remote interface.  It doesn't know about the true type of the object.
 

/*File Rmi05RemoteIntfcA.java
**********************************************************/
//snip

public interface Rmi05RemoteIntfcA extends Remote{
  Date getDateObj() throws RemoteException;

  Rmi05RemoteIntfcB getRemObj() throws RemoteException;

}//end Rmi05RemoteIntfcA definition

The next fragment shows the important parts of the Remote interface for the object that is embedded as an instance variable of the main remote object.  This is the object that is returned by the getRemObj() method discussed above.

There are no surprises here.  This interface simply declares a pair of set and get methods for setting and getting the value of an instance variable of the class.  Declaring them in the Remote interface exposes them and makes them available to the client if the client can get a handle on the object.  All methods that are declared in the interface are exposed to the client, and all methods of the class that are not declared in the interface are not exposed to the client.
 

/*File Rmi05RemoteIntfcB.java
**********************************************************/
//snip

public interface Rmi05RemoteIntfcB extends Remote{
  void setData(int data) throws RemoteException;

  int getData() throws RemoteException;

}//end Rmi05RemoteIntfcB definition

The Remote Object Files

This program uses two different types of remote objects.  One type is the main remote object on which methods are originally invoked.  An object of this type contains an instance variable that is also a remote object.

Once the client gains access to the remote instance variable via the get method in the main object, the client can then invoke methods on the remote object as well, because the methods are declared in the Remote interface described above.

The following fragment shows the important parts of the class definition for the main remote object.

As mentioned earlier, an object of this class has two instance variables.  One, a Date object is an ordinary object because the Date class does not implement the Remote interface.  A get method named getDateObj() is associated with this instance variable.  When this method is invoked remotely, a copy of the object will be serialized and sent to the client.

The other instance variable named theRemoteObj is a Remote object because it is of a class that implements the Remote interface.  It also has an associated get method named getRemObj().  When this method is invoked, a stub is sent to the client representing the actual object.  A copy of the object is not serialized and sent to the client.  The client then has access to the actual object via the stub as a reference.
 

/*File Rmi05RemoteObjA.java
**********************************************************/
//snip

public class Rmi05RemoteObjA extends UnicastRemoteObject
                              implements Rmi05RemoteIntfcA{

  private Date theDate = new Date();
  private Rmi05RemoteObjB theRemoteObj;
  
  public Rmi05RemoteObjA() throws RemoteException{
    theRemoteObj = new Rmi05RemoteObjB(500);
  }//end constructor

  public Date getDateObj() throws RemoteException{
    return theDate;
  }//end getDateObj()
    
  public Rmi05RemoteIntfcB getRemObj() 
                                    throws RemoteException{
    return theRemoteObj;
  }//end getRemObj()
    
}//end class Rmi05RemoteObjA

The next fragment shows important parts of the class definition for the other remote object.  This is the remote object that is embedded as an instance variable in the main remote object.

Except for the fact that this class implements the Remote interface indirectly through inheritance, there is nothing remarkable about the class definition.  It has a simple primitive instance variable of type int.  It has a pair of set and get methods to set and get the value of the instance variable.

The one remarkable thing is that because this class implements the Remote interface, the methods declared in that interface are exposed to the client.  Once the client gains access to a stub representing an object of this class, it can invoke the methods of the class on that stub which has the effect of invoking the methods on the object remotely.
 

/*File Rmi05RemoteObjB.java
**********************************************************/
//snip

public class Rmi05RemoteObjB extends UnicastRemoteObject
                             implements Rmi05RemoteIntfcB{

  private int data;
  
  public Rmi05RemoteObjB(int data) throws RemoteException{
    this.data = data;//initialize the object
  }//end constructor

  public void setData(int data) throws RemoteException{
    this.data = data;
  }//end setData()
    
  public int getData() throws RemoteException{
    return data;
  }//end getData()
    
}//end class Rmi05RemoteObjB

The Server Code File

The important parts of the server code for this program are shown below.  Perhaps the most important thing about this code is not what you see, but rather what you don't see.  For example, you don't see two remote objects being registered for exposure to the client.  Rather, only the main object is exposed to the client by binding it to a name in the registry.
 

/*File Rmi05Server.java
**********************************************************/
//snip

      Rmi05RemoteObjA remoteObjA = new Rmi05RemoteObjA();

      //Had to register it this way to avoid Win95 error
      LocateRegistry.createRegistry(1099);

      Naming.rebind("theRemoteObjectA", remoteObjA);
//snip

However, the client program can invoke methods on two different remote objects.  One of those objects is accessed by obtaining access via the registry.  The other object is accessed by gaining access to the first object. 

The second object is an instance variable of the first, and a get method is provided to gain access to the instance variable.  Once the client gains access to the first object, it also has access to the remote methods of the second object.

When the client invokes the get method on the main object to get access to the second object, it receives a stub representing the second object and can invoke remote methods on the second object by way of the stub.

The Client Code File

The important parts of the client code are shown below.  The output from running this program is shown following the discussion of the fragment.  The program contains several print statements to make it possible to track what is going on when viewing the output.

This code goes to the registry on the server and gets a reference to the main remote object.  The name of the reference is  refToObjA.

It uses that reference to invoke the getDateObj() method on the remote object.  This returns an object of type Date.  This is not a remote object, so an actual copy of the date object in the remote object is received.

In a nutshell, the code displays the date, modifies the date, and displays it again.  We will see that it has changed at that point as expected.  Then the code goes back to the remote object and gets the date object again and displays its contents.  As expected, those contents have not been changed, because the change to the date was made to a copy of the object and not to the original object.
 

/*File Rmi05Client.java
**********************************************************/
//snip
 
    String partOfUrl = "rmi://localhost/"; 
    try{
      Rmi05RemoteIntfcA refToObjA = 
                         (Rmi05RemoteIntfcA)Naming.lookup(
                           partOfUrl + "theRemoteObjectA");

      System.out.println("Get date obj from server");
      Date theDate = refToObjA.getDateObj();
      System.out.println("Date in obj is: " 
                                      + theDate.getDate());
      System.out.println("Set date in obj to 15");
      theDate.setDate(15);
      System.out.println("Date in obj is: " 
                                      + theDate.getDate());
      System.out.println("Get date obj from server");
      theDate = refToObjA.getDateObj();
      System.out.println("Date in obj is: " 
                                      + theDate.getDate());
      
      System.out.println("Get remote obj from server");
      Rmi05RemoteIntfcB theObj = refToObjA.getRemObj();      
      System.out.println("Value in obj is: " 
                                       + theObj.getData());
      System.out.println("Set value in obj to 999");
      theObj.setData(999);
      System.out.println("Value in obj is: " 
                                       + theObj.getData());
      System.out.println("Get remote obj from server");
      theObj = refToObjA.getRemObj();      
      System.out.println("Value in obj is: " 
                                       + theObj.getData());
//snip

Then the program goes through a similar series of steps with respect to the remote object that is an instance variable of the main remote object.  Here, the results are different.

The code gets the object and displays the data contained in that object.  Then it changes the value of the data stored in the object and displays it again.  As expected, the value has changed.  Then it goes back and gets the object again, and displays the value of the data stored in that object.  This time, unlike the Date object, the change was made to the instance variable contained in the main remote object (and not to a copy) so the change persists.

In fact, the client program is run a second time, and the change that was made during the first running of the program persists and shows up at the beginning of the second running of the program.

The first running of the client program produced the following output.  I manually inserted a blank line between the Date results and the remote object results to make it easier to read.  Note that the date information temporarily changes to 15 and then reverts back to 10.  However, once the remote object data value changes to 999, it stays there.
 

Get date obj from server
Date in obj is: 10
Set date in obj to 15
Date in obj is: 15
Get date obj from server
Date in obj is: 10

Get remote obj from server
Value in obj is: 500
Set value in obj to 999
Value in obj is: 999
Get remote obj from server
Value in obj is: 999

The second running of the client program produced the following output.  Because these two runs were made without shutting down the remote object in between, the data value stored in the embedded remote object started at 999, as left over from the first running, instead of starting at 500.
 

Get date obj from server
Date in obj is: 10
Set date in obj to 15
Date in obj is: 15
Get date obj from server
Date in obj is: 10

Get remote obj from server
Value in obj is: 999
Set value in obj to 999
Value in obj is: 999
Get remote obj from server
Value in obj is: 999

Complete listings of all of the code are provided in the next section.

Program Listings

rem File Rmi05.bat
rem Rev 10/09/98
echo off
echo Make certain that you have a Client folder and
echo  a Server folder immediately below this one.

echo Delete residue from previous run
del Rmi05*.class
del Client\*.class
del Server\*.class

echo Compile files required by Server
jvc Rmi05Server.java

echo Compile files required by Client
jvc Rmi05Client.java

echo Run rmic utility to create skeleton and stub classes
rmic Rmi05RemoteObjA
rmic Rmi05RemoteObjB

echo Put a copy of the stub class in the Serverfolder
copy Rmi05RemoteObjA_Stub.class Server
copy Rmi05RemoteObjB_Stub.class Server

echo Put a copy of the skeleton class in the Server folder
copy Rmi05RemoteObjA_Skel.class Server
copy Rmi05RemoteObjB_Skel.class Server

echo Put a copy of the remote obj class file in Server
copy Rmi05RemoteObjA.class Server
copy Rmi05RemoteObjB.class Server

echo Put a copy of the remote server class file in Server
copy Rmi05Server.class Server

echo Put copy of remote interface class file in Server
copy Rmi05RemoteIntfcA.class Server
copy Rmi05RemoteIntfcB.class Server

echo Put copy of client class file in Client folder
copy Rmi05Client.class Client

echo Put a copy of the stub class in the Client folder
copy Rmi05RemoteObjA_Stub.class Client
copy Rmi05RemoteObjB_Stub.class Client

echo Put copy of remote interface class file in Client
copy Rmi05RemoteIntfcA.class Client
copy Rmi05RemoteIntfcB.class Client

echo Should be able to start rmiregistry here, but it gives
echo  fatal error on my Win95 with jdk1.1.6.  Start it
echo  in server program instead.

echo Wait for remote object to become ready.  Then click 
echo  main window to keep things moving.

echo Start the server running in a different MSDOS window
cd Server
start java Rmi05Server
cd ..

echo Run client program twice to demo that remote object
echo  stays alive.
cd Client
java Rmi05Client
java Rmi05Client
cd..

echo Manually terminate the remote object.

.

/*File Rmi05RemoteIntfcA.java
Rev 10/10/98.
This is an RMI interface definition designed to show the 
difference between passing/returning remote objects and 
ordinary objects which don't implement the Remote 
interface. This interface extends the Remote interface.
Tested using JDK 1.1.6 under Win95.
**********************************************************/

import java.rmi.*;
import java.util.*;

public interface Rmi05RemoteIntfcA extends Remote{

  Date getDateObj() throws RemoteException;

  Rmi05RemoteIntfcB getRemObj() throws RemoteException;

}//end Rmi05RemoteIntfcA definition

.

/*File Rmi05RemoteIntfcB.java
Rev 10/10/98.
This is an RMI interface definition designed to show the 
difference between passing/returning remote objects and 
ordinary objects which don't implement the Remote 
interface. This interface extends the Remote interface.
Tested using JDK 1.1.6 under Win95.
**********************************************************/

import java.rmi.*;

public interface Rmi05RemoteIntfcB extends Remote{
  void setData(int data) throws RemoteException;

  int getData() throws RemoteException;

}//end Rmi05RemoteIntfcB definition

.

/*File Rmi05RemoteObjA.java
Rev 10/10/98.
This is an RMI Remote class designed to show the difference
between passing/returning remote objects and ordinary
objects, which don't implement the Remote interface. This
class implements the Remote interface indirectly through
inheritance.
Tested using JDK 1.1.6 under Win95.
**********************************************************/

import java.rmi.*;
import java.rmi.server.*;
import java.util.*;

public class Rmi05RemoteObjA extends UnicastRemoteObject
                              implements Rmi05RemoteIntfcA{

  private Date theDate = new Date();
  private Rmi05RemoteObjB theRemoteObj;
  
  public Rmi05RemoteObjA() throws RemoteException{
    theRemoteObj = new Rmi05RemoteObjB(500);
  }//end constructor

  public Date getDateObj() throws RemoteException{
    return theDate;
  }//end getDateObj()
    
  public Rmi05RemoteIntfcB getRemObj() 
                                    throws RemoteException{
    return theRemoteObj;
  }//end getRemObj()
    
}//end class Rmi05RemoteObjA

.

/*File Rmi05RemoteObjB.java
Rev 10/10/98.
This is an RMI Remote class designed to show the difference
between passing/returning remote objects and ordinary
objects, which don't implement the Remote interface. This
class implements the Remote interface indirectly through
inheritance.
Tested using JDK 1.1.6 under Win95.
**********************************************************/

import java.rmi.*;
import java.rmi.server.*;

public class Rmi05RemoteObjB extends UnicastRemoteObject
                             implements Rmi05RemoteIntfcB{

  private int data;
  
  public Rmi05RemoteObjB(int data) throws RemoteException{
    this.data = data;//initialize the object
  }//end constructor

  public void setData(int data) throws RemoteException{
    this.data = data;
  }//end setData()
    
  public int getData() throws RemoteException{
    return data;
  }//end getData()
    
}//end class Rmi05RemoteObjB

.

/*File Rmi05Server.java
Rev 10/10/98.
This is an RMI server designed to show the difference 
between passing/returning remote objects and ordinary
objects, which don't implement the Remote interface.
Tested using JDK 1.1.6 under Win95.
**********************************************************/

import java.rmi.*;
import java.rmi.server.*;
import sun.applet.*;
import java.rmi.registry.LocateRegistry;

public class Rmi05Server{
  
  public static void main(String args[]){
    System.setSecurityManager(new RMISecurityManager());
      
    try{
      Rmi05RemoteObjA remoteObjA = new Rmi05RemoteObjA();

      //Had to register it this way to avoid Win95 error
      LocateRegistry.createRegistry(1099);

      Naming.rebind("theRemoteObjectA", remoteObjA);
      
      System.out.println("Remote obj ready to use");
    }catch(Exception e){System.out.println("Error: " + e);}
  }//end main
}//end class Rmi05Server

.

/*File Rmi05Client.java
Rev 10/10/98.
This is an RMI client designed to show the difference 
between passing/returning remote objects and ordinary
objects, which don't implement the Remote interface.
Tested using JDK 1.1.6 under Win95.
**********************************************************/

import java.rmi.*;
import java.rmi.server.*;
import java.util.*;

public class Rmi05Client{
  
  public static void main(String[] args){
    System.setSecurityManager(new RMISecurityManager()); 
    String partOfUrl = "rmi://localhost/"; 
    try{
      Rmi05RemoteIntfcA refToObjA = 
                         (Rmi05RemoteIntfcA)Naming.lookup(
                           partOfUrl + "theRemoteObjectA");

      System.out.println("Get date obj from server");
      Date theDate = refToObjA.getDateObj();
      System.out.println("Date in obj is: " 
                                      + theDate.getDate());
      System.out.println("Set date in obj to 15");
      theDate.setDate(15);
      System.out.println("Date in obj is: " 
                                      + theDate.getDate());
      System.out.println("Get date obj from server");
      theDate = refToObjA.getDateObj();
      System.out.println("Data in obj is: " 
                                      + theDate.getDate());
      
      System.out.println("Get remote obj from server");
      Rmi05RemoteIntfcB theObj = refToObjA.getRemObj();      
      System.out.println("Value in obj is: " 
                                       + theObj.getData());
      System.out.println("Set value in obj to 999");
      theObj.setData(999);
      System.out.println("Value in obj is: " 
                                       + theObj.getData());
      System.out.println("Get remote obj from server");
      theObj = refToObjA.getRemObj();      
      System.out.println("Value in obj is: " 
                                       + theObj.getData());

    }catch(Exception e){System.out.println("Error " + e);}
    System.exit(0);
  }//end main
}//end class Rmi05Client

-end-