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). While you work through this example, you will probably come up with a number of related questions. You may find the answer in the RMI FAQ, or you may wish to look in the email archives of the rmi-users alias. If you'd like to subscribe to the rmi-users email alias, click here.
The distributed Hello World example uses an applet to make a remote method call to the server, from which the applet was downloaded, to retrieve the message "Hello World!". When the applet runs, "Hello World!" is displayed on the client browser.
This tutorial is organized as follows:
Hello.java
HelloImpl.java,
which implements examples.hello.Hello
HelloApplet.java, that invokes
the remote method, sayHello
hello.html,
that references the applet
examples.hello.HelloImpl,
which implements a remote interface.
For all of the source code used in this tutorial, you may choose from the following formats:
examples.hello and the source
directory is $HOME/mysrc/examples/hello.
To create the directory for your source files on Solaris, execute the command:
mkdir -p $HOME/mysrc/examples/hello
On Windows platforms, you would go to the directory of your choice, and
type:
mkdir mysrc
mkdir mysrc\examples
mkdir mysrc\examples\hello
There are three tasks to complete in this section:
In Java, 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 remotely. Remote interfaces have the following characteristics: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
- The remote interface must be declared public. Otherwise, unless a client is in the same package as the remote interface, the client will get an error when attempting to load a remote object that implements the remote interface.
- The remote interface extends the
java.rmi.Remoteinterface.- Each method must declare
java.rmi.RemoteException(or a superclass ofRemoteException) in its throws clause, in addition to any application-specific exceptions.- The data type of any remote object that is passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface type (for example,
Hello) not the implementation class (HelloImpl).Here is the interface definition for the remote interface,
examples.hello.Hello. The interface contains just one method,sayHello, which returns a string to the caller:
java.rmi.RemoteException. If you want more information on failure and recovery in distributed systems, you may wish to read A Note on Distributed Computing.Write the implementation and server classes
At a minimum, a remote object implementation class must:
A "server" class in this context, is the class which has a
- Declare that it implements at least one remote interface.
- Define the constructor for the remote object.
- Provide implementations for the methods that can be invoked remotely.
mainmethod that creates an instance of the remote object implementation, and binds that instance to a name in thermiregistry. The class that contains thismainmethod could be the implementation class itself, or another class entirely.In this example, the
mainmethod is part ofexamples.hello.HelloImpl. The server program needs to:An explanation of each of the preceding six steps follows the source for
- Create and install a security manager.
- Create one or more instances of a remote object.
- Register at least one of the remote objects with the RMI remote object registry, for bootstrapping purposes.
HelloImpl.java:
package examples.hello; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.RMISecurityManager; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements Hello { public HelloImpl() throws RemoteException { super(); } public String sayHello() { return "Hello World!"; }public static void main(String args[]) {
// Create and install a security managerif (System.getSecurityManager() == null) {System.setSecurityManager(new RMISecurityManager());}try {HelloImpl obj = new HelloImpl();// Bind this object instance to the name "HelloServer"Naming.rebind("//myhost/HelloServer", obj);System.out.println("HelloServer bound in registry");} catch (Exception e) {System.out.println("HelloImpl err: " + e.getMessage());e.printStackTrace();}}}Implement a remote interfaceWrite a client program that uses the remote service
In the Java 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 the interface that it is implementing. Interface methods are implicitlypublicandabstract, so if the implementation class doesn't fufill it's contract, it becomes by definition anabstractclass, and the compiler will point out this fact if the class was not declaredabstract.The implementation class in this example is
examples.hello.HelloImpl. The implementation class declares which remote interface(s) it is implementing. Here is theHelloImplclass declaration:As a convenience, the implementation class can extend a remote class, which in this example is
public class HelloImpl extends UnicastRemoteObject implements Hellojava.rmi.server.UnicastRemoteObject. By extendingUnicastRemoteObjecttheHelloImplclass can be used to create a remote object that:If you want a remote object that can be activated (created) when a client requests it, rather than running all the time, after you finish this tutorial, you can take a look at the Remote Object Activation tutorial. Also, you can learn about how to use your own communication protocol, rather than the TCP sockets that RMI uses by default, in the tutorial on Creating a Custom RMI socket factory.
- Uses RMI's default sockets-based transport for communication
- Runs all the time
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, your 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
java.rmi.server.UnicastRemoteObjectorjava.rmi.activation.Activatable, your class will be automatically exported upon creation.If you choose to extend a remote object from any class other than
UnicastRemoteObjectorActivatable, you will need to explicitly export the remote object by calling either theUnicastRemoteObject.exportObjectmethod or theActivatable.exportObjectmethod from your class's constructor (or another initialization method, as appropriate).Because the object export could potentially throw a
java.rmi.RemoteException, you must define a constructor that throws aRemoteException, even if the constructor does nothing else. If you forget the constructor,javacwill produce the following error message:To review: The implementation class for a remote object needs to:HelloImpl.java:13: Exception java.rmi.RemoteException must be caught, or it must be declared in the throws clause of this method.super();^1 errorHere is the constructor for the
- Implement a remote interface
- Export the object so that it can accept incoming remote method calls
- Declare its constructor(s) to throw at least a
java.rmi.RemoteExceptionexamples.hello.HelloImplclass:Note the following:
public HelloImpl() throws RemoteException {super();}Although the call to the superclass's no-argument constructor,
- The
supermethod call invokes the no-argument constructor ofjava.rmi.server.UnicastRemoteObject, which exports the remote object.- The constructor must throw
java.rmi.RemoteException, because RMI's attempt to export a remote object during construction might fail if communication resources are not available.super(), occurs by default (even if omitted), it is included in this example to make clear the fact that the Java VM constructs the superclass 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 thesayHellomethod, which returns the string "Hello World!" to the caller:Arguments to, or return values from, remote methods can be of any Java type, including objects, as long as those objects implement the interface
public String sayHello() throws RemoteException { return "Hello World!"; }java.io.Serializable. Most of the core Java classes injava.langandjava.utilimplement theSerializableinterface. In RMI:A class can define methods not specified in the remote interface, but those methods can only be invoked within the virtual machine running the service and cannot be invoked remotely.
- By default, local objects are passed by copy, which means that all data members (or fields) of an object are copied, except those marked as
staticortransient. Please refer to the Java Object Serialization Specification for information on how to alter the default serilazation behavior.- Remote objects are passed by reference. A reference to a remote object is actually a reference to a stub, which is a client-side proxy for the remote object. Stubs are described fully in the RMI Specification. We'll create them later in this tutorial in the section: Use
rmicto generate stubs, and optionally, skeletons.Create and install a security manager
Themainmethod of the server first needs to create and install a security manager: either theRMISecurityManageror one that you have defined yourself. For example:A security manager needs to be running so that it can guarantee that the classes that get loaded do not perform operations that they are not allowed to perform. If no security manager is specified no class loading, by RMI clients or servers, is allowed, aside from what can be found in the local CLASSPATH.
if (System.getSecurityManager() == null) {System.setSecurityManager(new RMISecurityManager());}Create one or more instances of a remote object
Themainmethod of the server needs to create one or more instances of the remote object implementation which provides the service. For example:The constructor exports the remote object, which means that once created, the remote object is ready to accept incoming calls.
HelloImpl obj = new HelloImpl();Register the remote object
For a caller (client, peer, or applet) to be able to invoke a method on a remote object, that caller must first obtain a reference to the remote object.For bootstrapping, the RMI system provides a remote object registry that allows you to bind a URL-formatted name of the form "
//host/objectname"to the remote object, whereobjectnameis a simple string name.The RMI registry is a simple server-side name server that allows remote clients to get a reference to a remote object. It is typically used only to locate the first remote object an RMI client needs to talk to. Then that first object in turn, would provide application-specific support for finding other objects.
For example, the reference can be obtained as a parameter to, or a return value from, another remote method call. For a discussion on how this works, please take a look at Applying the Factory Pattern to RMI.
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.
For example, the following code binds the name "HelloServer" to a reference for the remote object:
Note the following about the arguments to the
Naming.rebind("//myhost/HelloServer", obj);rebindmethod call:For security reasons, an application can bind or unbind only to a registry running on the same host. This prevents a client from removing or overwriting any of the entries in a server's remote registry. A lookup, however, can be done from any host.
- The first parameter is a URL-formatted
java.lang.Stringrepresenting the location and name of the remote object.
- You will need to change the value of
myhostto be the name or IP address of your server machine. Otherwise, the host defaults to the current host if omitted from the URL, and no protocol needs to be specified in the URL: for example "HelloServer.".- Optionally, a port number can be supplied in the URL: for example "/
/myhost:1234/HelloServer".The port defaults to 1099. It is necessary to specify the port number only if a server creates a registry on a port other than the default 1099.- The second parameter is the object implementation reference on which remote methods will be invoked.
- The RMI runtime substitutes a reference to the remote object's stub for the actual remote object reference specified by the
objargument. Remote implementation objects like instances ofHelloImplnever leave the virtual machine where they are created, so when a client performs a lookup in a server's remote object registry, an object that contains the stub for the implementation is returned.The applet part of the distributed Hello World example remotely invokes the
sayHellomethod in order to get the string "Hello World!", which is displayed when the applet runs. Here is the code for the applet:
package examples.hello;
import java.applet.Applet;import java.awt.Graphics;import java.rmi.Naming;import java.rmi.RemoteException;
public class HelloApplet extends Applet {
String message = "blank";// "obj" is the identifier that we'll use to refer// to the remote object that implements the "Hello"// interfaceHello obj = null;
public void init() {try {obj = (Hello)Naming.lookup("//" +getCodeBase().getHost() + "/HelloServer");message = obj.sayHello();} catch (Exception e) {System.out.println("HelloApplet exception: " +e.getMessage());e.printStackTrace();}}
public void paint(Graphics g) {g.drawString(message, 25, 50);}}The constructed URL-string that is passed as a prameter to the
- First, the applet gets a reference to the remote object implementation (advertised as "HelloServer") from the server host's
rmiregistry.Like theNaming.rebindmethod, theNaming.lookupmethod takes a URL-formattedjava.lang.String. In this example, the applet constructs the URL string by using thegetCodeBasemethod in conjunction with thegetHostmethod.Naming.lookuptakes care of the following tasks:
- Contructs a registry stub instance (to contact the server's registry) using the hostname and port number supplied as arguments to
Naming.lookup- Uses the registry stub to call the remote
lookupmethod on the registry, using the URL's name component ("HelloServer")
- The registry returns the
HelloImpl_Stubinstance bound to that name- Receives the remote object implementation (
HelloImpl) stub instance and loads the stub class (examples.hello.HelloImpl_Stub) from the CLASSPATH or the stub's codebaseNaming.lookupreturns the stub to its caller (HelloApplet)- The applet invokes the remote
sayHellomethod on the server's remote object
- RMI serializes and returns the reply string "Hello World!"
- RMI deserializes the string and stores it in a variable named
message.
- The applet invokes the
paintmethod, causing the string "Hello World!" to be displayed in the drawing area of the applet.Naming.lookupmethod must include the server's hostname. Otherwise, the applet's lookup attempt will default to the client, and theAppletSecurityManagerwill throw an exception since the applet cannot access the local system, but is instead limited to communicating only with the applet's host.Here is the HTML code for the web page that references the Hello World applet:
Note the following:
<HTML><title>Hello World</title><center> <h1>Hello World</h1> </center>
The message from the HelloServer is:<p><applet codebase="myclasses/"code="examples.hello.HelloApplet"width=500 height=120></applet></HTML>
- There needs to be an HTTP server running on the machine from which you want to download classes.
- The codebase in this example specifies a directory below the directory from which the web page was itself loaded. Using this kind of relative path is usually a good idea. For example, if the
codebasedirectory (where the applet's class files live), referenced by the applet's HTML, was in the directory above the the HTML directory, you would want to use a relative path like "../".- The applet's
codeattribute specifies the fully-qualified package name of the applet, in this exampleexamples.hello.HelloApplet:
code="examples.hello.HelloApplet"
$HOME/mysrc/examples/hello
directory has four files:
Hello.java, which contains the source code for the Hello remote
interface.
HelloImpl.java, which is the source code for the HelloImpl
remote object implementation, the server for the Hello World applet.
HelloApplet.java, which is the source code for the applet.
hello.html, which is the web page that references the Hello World
applet.
.java source files to create
.class files. 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 calls to the server-side dispatcher, which in turn forwards
the call to the actual remote object implementation.
When you use the javac and rmic compilers, you must
specify where the resulting class files should reside. For applets, all
files should be in the applet's codebase directory. For our example, this
directory is $HOME/public_html/myclasses.
Some Web servers allow accessing a user's public_html directory via an HTTP URL constructed as "http://host/~username/". If your Web server does not support this convention, you may use a file URL of the form "file://home/username/public_html".
There are four tasks to complete in this section:
$HOME/public_html/myclasses
and the development directory $HOME/mysrc/examples/hello are each
accessible through the local CLASSPATH on the development machine,
before attempting to compile.
To compile the Java source files, run the javac command as
follows:
javac -d $HOME/public_html/myclasses
Hello.java HelloImpl.java HelloApplet.java
examples/hello (if it does
not already exist) in the directory $HOME/public_html/myclasses.
The command then writes to that directory the files Hello.class,
HelloImpl.class, and HelloApplet.class. These are the
remote interface, the implementation, and the applet respectively.
For an explanation of javac options, you can refer to the Solaris
javac manual page or the Win32
javac manual page.
rmic to generate skeletons and/or stubsrmic compiler on the
fully-qualified package names of compiled class files that contain remote
object implementations, like my.package.MyImpl. The rmic
command takes one or more class names as an argument and produces class
files of the form MyImpl_Skel.class and MyImpl_Stub.class.
By default, in JDK 1.2, rmic runs with the -vcompat flag
on, which produces stubs and skeletons that support access to:
Activatable) remote objects from 1.1 clients and
rmic can be
run with the -v1.2 option. For an explanation of rmic
options, you can refer to the Solaris
rmic manual page or the Win32
rmic manual page.
For example, to create the stub and skeleton for the HelloImpl remote
object implementation, run rmic like this:
rmic -d $HOME/public_html/myclasses examples.hello.HelloImpl
-d option indicates the root directory in which to place the
compiled stub and skeleton class files. So the preceding command creates
the following files in the directory $HOME/public_html/myclasses/examples/hello:
HelloImpl_Stub.class
HelloImpl_Skel.class
hello.html file must be moved from the development directory to
the applet's codebase directory. For example:
mv $HOME/mysrc/examples/hello/hello.html $HOME/public_html/
HOME/public_html/codebase directory is available
through the server's local CLASSPATH when you run the HelloImpl
server. Start the RMI registry
The RMI registry is a simple server-side name server that allows remote clients to get a reference to a remote object. It is typically used only to locate the first remote object an application needs to talk to. Then that object in turn would provide application-specific support for finding other objects.Note: Before you start the rmiregistry, you must make sure that the shell or window in which you will run the
registry, either has no CLASSPATH set or has a CLASSPATH that does not include the path to any classes that you want downloaded to your client, including the stubs for your remote object implementation classes.If you start the
rmiregistry, and it can find your stub classes in its CLASSPATH, it will ignore the server'sjava.rmi.server.codebaseproperty, and as a result, your client(s) will not be able to download the stub code for your remote object.To start the registry on the server, execute the
rmiregistrycommand. This command produces no output and is typically run in the background. For more on thermiregistry, you can refer to the Solarisrmiregistrymanual page or the Win32rmiregistrymanual page.
For example, on Solaris:
rmiregistry &For example, on Windows 95 or Windows NT:
(Use
start rmiregistryjavawif start is not available.)The registry by default runs on port 1099. To start the registry on a different port, specify the port number from the command-line. For example, to start the registry on port 2001 on a Windows NT system:
If the registry is running on a port other than the default, you need to specify the port number in the name handed to the URL-based methods of the
start rmiregistry 2001java.rmi.Namingclass when making calls to the registry. For example, if the registry is running on port 2001 in the Hello World example, the call required to bind the URL of the HelloServer to the remote object reference would be:You must stop and restart the registry 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 registry will not match the modified class.
Naming.rebind("//myhost:2001/HelloServer", obj);
Start the server
When starting the server, thejava.rmi.server.codebaseproperty must be specified, so that the stub class can be dynamically downloaded to the registry and then to the client. Run the server, setting the codebase property to be the location of the implementation stubs. Since the codebase property can only reference a single directory, make sure that any other classes that may need to be downloaded, have also been installed in the directory referenced byjava.rmi.server.codebase.For explanations of each of the
java.rmi.serverproperties, click here. To see all the availablejava.rmi.activationproperties, click here. For an explanation ofjavaoptions, you can refer to the Solarisjavamanual page or the Win32javamanual page. If you have problems running the example code, please take a look at the RMI and Serialization FAQ.Note: A stub class is dynamically downloaded to a client's virtual machine only when the class is not already available locally and the
java.rmi.server.codebaseproperty has been set properly, to where the class files live, on the server.There are four things that need to go on the same command line: the "
java" command, followed by two property name=value pairs (for thecodebaseproperty, note that there are no spaces from the "-D" all the way though the last "/") and then the fully-qualified package name of the server program. There should be a space just after the word "java", between the two properties and just before the word "examples" (which is very hard to see when you view this as text, in a browser, or on paper). The following command shows how to start theHelloImplserver, specifying thejava.rmi.server.codebaseandjava.security.policyproperties:
java -Djava.rmi.server.codebase=http://myhost/~myusrname/myclasses/ -Djava.security.policy=$HOME/mysrc/policy examples.hello.HelloImpl
In order to run this code on your system, you'll need to change the location of thepolicyfile to be the location of the directory on your system, where you've installed the example source code. Note: In this example, for simplicity, we will use a policy file that gives global permission to anyone from anywhere. Do not use this policy file in a production environment. For more information on how to properly open up permissions using ajava.security.policyfile, please refer to to the following documents:
http://java.sun.com/products/jdk/1.2/docs/guide/security/PolicyFiles.htmlhttp://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.htmlThe codebase property will be resolved to a URL, so it must have the form of "
http://aHost/somesource/" or "file:/myDirectory/location/" or, due to the requirements of some operating systems, "file:///myDirectory/location/" (three slashes after the "file:").
Please note that each of these sample URL strings has a trailing "/". The trailing slash is a requirement for the URL set bythe java.rmi.server.codebaseproperty, so the implementation can resolve (find) your class definition(s) properly.
If you forget the trailing slash on thecodebaseproperty, or if the class files can't be located at the source (they aren't really being made available for download) or if you misspell the property name, you'll get thrown a java.lang.ClassNotFoundException. This exception will be thrown when you try to bind your remote object to thermiregistry, or when the first client attempts to access that object's stub. If the latter case occurs, you have another problem as well because thermiregistrywas finding the stubs in its CLASSPATH.The output should look like this:
HelloServer bound in registry
Run the applet
Once the registry and server are running, the applet can be run. An applet is run by loading its web page into a browser orappletviewer, as shown here:After running the appletviewer, you will see output similar to the following on your display:
appletviewer http://myhost/~myusrname/hello.html &
