This
tutorial shows you the steps to follow to create a distributed version of the
classic Hello World program using JavaTM Remote Method
Invocation (RMI).
Description
The
distributed Hello World example uses an applet to make a remote method call to
an RMI server, running on the host from which the applet was downloaded. When
the applet runs, "Hello World!" is displayed on the client browser.
This
example is organized as follows:
The files needed for this tutorial are:
Hello.java - a remote interface HelloImpl.java - a remote object implementation that
implements examples.hello.Hello HelloApplet.java - an applet that
invokes the remote method, sayHello hello.html - the HTML page that references the applet
Applet working
mode

RMI working mode

Because the JavaTM programming language
requires a mapping between the fully-qualified package name of a class and the
directory path to that class, you should decide on package and directory names
before you begin writing any code written in the Java programming language.
This mapping allows the compiler for the Java programming language to know the
directory in which to find the class files mentioned in a program. For the
programs in this example, the package name is examples.hello and the source
directory is $HOME/mysrc/examples/hello.
create
the directory for your source
On Microsoft 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 implementing RMI application:
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 JavaTM virtual machines (JVMs).
Remote interfaces have the following characteristics:
public. Otherwise,
a client will get an error when attempting to load a remote object that
implements the remote interface, unless that client is in the same package
as the remote interface. java.rmi.Remote interface. java.rmi.RemoteException (or a
superclass of RemoteException) in its throws clause, in addition to any application-specific
exceptions. 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:
package examples.hello; import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { String sayHello() throws 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 and server classes
At a
minimum, a remote object implementation class must:
A "server" class, in this context, is the class which:
main method that creates an instance of the remote
object implementation, and rmiregistry. main method could
be the implementation class itself, or another class entirely. In this
example, the main method is part of examples.hello.HelloImpl. The server
program needs to:
An explanation of each of the preceding six steps follows the source for
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 manager if (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(); } } }
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 examples.hello.HelloImpl. The
implementation class declares which remote interface(s) it is implementing.
Here is the HelloImpl class
declaration:
public class HelloImpl extends UnicastRemoteObject implements Hello {
As a convenience, the
implementation class can extend a remote class, which in this example is java.rmi.server.UnicastRemoteObject. By extending UnicastRemoteObject, the HelloImpl class can be used
to create a remote object that:
·
Uses RMI's default sockets-based transport for communication
·
Runs all the time
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 java.rmi.server.UnicastRemoteObject or java.rmi.activation.Activatable, your class will be exported
automatically upon creation.
If you choose to extend a
remote object from any class other than UnicastRemoteObject or Activatable, you will need to
explicitly export the remote object by calling either the UnicastRemoteObject.exportObject method or the Activatable.exportObject method from your class's
constructor (or another initialization method, as appropriate).
I recommend you to avoid
using this second way.
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:13: Exception java.rmi.RemoteException must be caught, or it must be declared in the throws clause of this method. super(); ^ 1 error
To review: The
implementation class for a remote object needs to:
·
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.RemoteException
Here is the constructor for the
examples.hello.HelloImpl class:
public HelloImpl() throws RemoteException { super(); }
Note the following:
·
The super method call invokes the
no-argument constructor of java.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.
Although the call to the
superclass's no-argument constructor, super(), occurs by
default (even if omitted), do always
include it to make clear on the
fact that the Java virtual machine (JVM) constructs the superclass before the
class.
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 World!" to the caller:
public String sayHello() throws RemoteException { return "Hello World!"; }
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:
·
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 static or transient.
·
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.
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.
The main method of the
server first needs to create and install a security manager: either the RMISecurityManager or one that you
have defined yourself. For example:
if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager());}
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. In this example, a security
manager is not installed in the client code because applets use the
security manager already installed in the client browser. If the client
were an application rather than an applet, however, you would need to use the
same procedure as is used above to install a security manager in the client. A
security manager is required in any JVM that needs to download code, and RMI
clients need to download RMI stubs (as well as any other custom classes or
interfaces needed to communicate with the RMI server).
The main method of the
server needs to create one or more instances of the remote object
implementation which provides the service. For example:
HelloImpl obj = new HelloImpl();
The constructor exports the
remote object, which means that once created, the remote object is ready to
accept incoming calls.
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, where objectname is a simple
string name.
The RMI registry is a simple
server-side name service 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 would in turn 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.
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:
Naming.rebind("//myhost/HelloServer",
obj);
Note the following about the
arguments to the rebind method call:
·
The first parameter is a URL-formatted java.lang.String, representing the
location and name of the remote object.
o
No protocol needs to be specified in the URL-formatted string.
o
You will need to change the value of myhost to be the name or
IP address of your server machine; otherwise, the remote object host defaults
to the current host. For example, "HelloServer" is a valid
name string that refers to a remote object bound to the name HelloServer, running on the
local host.
o
Optionally, a port number can be supplied in the URL-formatted string.
Specifying the port number is necessary when the registry that needs to be
contacted is running on a port other than the default port, 1099. For example,
"//myhost:1234/HelloServer" is a valid
name string for the HelloServer remote object,
reachable through an RMI registry that is running on the host myhost and is listening
for incoming calls on port 1234.
·
The second parameter is a reference to the object implementation, on
which remote methods will be invoked.
·
Once an object is exported, the RMI runtime substitutes a reference to
the remote object's stub for the actual remote object reference specified by the
obj argument. When a client performs a lookup in a
server's remote object registry, a serialized instance of the stub for the
implementation is returned.
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
applet in this example remotely invokes the sayHello method in order
to get the string "Hello World!" to display 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" // interface Hello 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); } }
rmiregistry. Like the Naming.rebind method, the Naming.lookup method takes a URL-formatted java.lang.String. In this example, the
applet constructs the URL string by using the getCodeBase method in conjunction with the getHost method. Naming.lookup takes care
of the following tasks: Naming.lookup lookup method on
the registry, using the URL's name component ("HelloServer") HelloImpl_Stub instance
bound to that name lookup method receives the remote object's (HelloImpl) stub instance and loads the stub class (examples.hello.HelloImpl_Stub) from the CLASSPATH
or the applet's codebase Naming.lookup returns the stub to its caller (HelloApplet) sayHello method on the server's
remote object message. paint method, causing the string "Hello
World!" to be displayed in the drawing area of the applet. The URL-formatted string that is passed as a parameter to the Naming.lookup method must
include the server's hostname. Otherwise, the applet's lookup attempt will
default to the client, and the AppletSecurityManager will throw an
exception because the applet cannot access the local system, but is instead
limited to only communicating with the applet's host.
Here is
the HTML code for the web page that references the Hello World applet:
<HTML> <title>Hello World</title> <center> <h1>Hello World</h1> </center><applet codebase="myclasses/"
code="examples.hello.HelloApplet"
width=500 height=120> </applet> </HTML>
Note the following:
codebase in the HTML file 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 codebase directory (where the applet's class files live),
referenced by the applet's HTML, was in the directory above the the HTML
directory, you would use the relative path, "../". code attribute specifies the fully-qualified package
name of the applet, in this example examples.hello.HelloApplet: · code="examples.hello.HelloApplet"
The source code for this example is now complete and the $HOME/mysrc/examples/hello directory has
four files:
Hello.java contains the
source code for the Hello remote
interface HelloImpl.java contains the
source code for the HelloImpl remote
object implementation and the RMI server for the applet HelloApplet.java contains the
source code for the applet hello.html is the web
page that references the Hello World applet. In this section, you compile the .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 could use a file URL of the form "file:/home/username/public_html" for testing, but
this approach will limit you to communicating between a client and server that
have access to the same physical file system
There are
four tasks to complete in this section:
rmic to generate stubs and skeletons Make sure that the deployment
directory $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 source files,
run the javac command as follows:
javac
-d $HOME/public_html/myclasses Hello.java HelloImpl.java
HelloApplet.java
This command creates the
directory 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 stubsTo create stub and skeleton
files, run the rmic 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 the Java 2
SDK, v1.2, rmic runs with the -vcompat
flag
on, which produces stubs and skeletons that support access to:
1.
Unicast (not Activatable) remote objects
from 1.1 clients and
2.
All types of remote objects from 1.2 clients
If you will only ever need
support for 1.2 clients, 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
The "-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
The generated stub class
implements exactly the same set of remote interfaces as the remote object
itself. This means that a client can use the Java programming language's
built-in operators for casting and type checking. It also means that remote
objects written for the Java platform support true object-oriented
polymorphism.
To make the web page that
references the applet visible to clients, the 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/
Make sure that the $HOME/public_html/myclasses directory is
available through the server's local CLASSPATH when you run the HelloImpl server.
There are three tasks to complete in this section:
The RMI registry is a simple
server-side name server that allows remote clients to get a reference to a
remote object. Typically, it is 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's java.rmi.server.codebase property, 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 rmiregistry command. This
command produces no output and is typically run in the background.
For example, in the Solaris
operating environment:
rmiregistry &
For example, on Microsoft
Windows 95 systems:
start rmiregistry
(Use javaw if start is not
available.)
By default, the registry 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 Microsoft Windows NT system:
start rmiregistry 2001
If the registry is running on
a port other than 1099, you'll need to specify the port number in the name
handed to the URL-based methods of the java.rmi.Naming class when making
calls to the registry. For example, if the registry is running on port 2001 in
this example, the call required to bind the name "HelloServer" to the
remote object reference would be:
Naming.rebind("//myhost:2001/HelloServer", obj);
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.
When starting the server, the
java.rmi.server.codebase property 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. Because the codebase property in this
example references a directory, make sure that any other classes that may need
to be downloaded have also been installed in the directory referenced by java.rmi.server.codebase.
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.codebase property has
been set properly to specify where the class files are located 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 the codebase property, 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 the HelloImpl server,
specifying the java.rmi.server.codebase and java.security.policy properties:
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 the policy file 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 containing the following
grant { // Allow everything for nowpermission java.security.AllPermission;
};
that gives global permission to anyone from anywhere
The 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:").
Note that each of the URL strings above has a trailing "/".
The trailing slash is a requirement for the URL set by the
java.rmi.server.codebase property, so the implementation can resolve (find)
your class definition(s) properly.
If you forget the trailing slash on the codebase property, 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 the rmiregistry, or when the
first client attempts to access that object's stub. If the latter case occurs,
you have another problem as well because the rmiregistry was 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 or appletviewer, as shown here:
appletviewer http://myhost/~myusrname/hello.html &
After running the appletviewer, you will see output similar to the
following on your display:
