java.rmi Package
The The An The The Here's a result from a run against the RMI server I was using to test the examples in this chapter:
You can see that the format for the strings is full rmi URLs rather than just names. It turns out this is a bug; in Java 1.4.1 and later, the bug has been fixed. In these versions, the scheme part of the URI is no longer included. In other words, the output looks like this:
A client uses the A server uses the The The A client loads stubs from a potentially untrustworthy server; in this sense, the relationship between a client and a stub is somewhat like the relationship between a browser and an applet. Although a stub is only supposed to marshal arguments and unmarshal return values and send them across the network, from the standpoint of the virtual machine, a stub is just another class with methods that can do just about anything. Stubs produced by rmic shouldn't misbehave; but there's no reason someone couldn't handcraft a stub that would do all sorts of nasty things, such as reading files or erasing data. The Java virtual machine does not allow stub classes to be loaded across the network unless there's some In Java 1.1, this class implements a policy that allows classes to be loaded from the server's codebase (which is not necessarily the same as the server itself) and allows the necessary network communications between the client, the server, and the codebase. In Java 1.2 and later, the The More robust programs should try to catch more specific exceptions and respond accordingly.
Exception
Meaning
A client tried to do something that only local objects are allowed to do.
The URL is already bound to another object.
The server refused the connection.
An I/O error occurred while trying to make the connection between the local and the remote host.
An I/O error occurred while attempting to marshal (serialize) arguments to a remote method. A corrupted I/O stream could cause this exception; making the remote method call again might be successful.
An I/O error occurred while attempting to unmarshal (deserialize) the value returned by a remote method. A corrupted I/O stream could cause this exception; making the remote method call again might be successful.
The object reference is invalid or obsolete. This might occur if the remote host becomes unreachable while the program is running, perhaps because of network congestion, system crash, or other malfunction.
The URL is not bound to an object. This might be thrown when you try to reference an object whose URL was rebound out from under it.
The generic superclass for all exceptions having to do with remote methods.
Despite the name, this is indeed an exception, not an error. It indicates that the server threw an error while executing the remote method.
A The stub for a class could not be found. The stub file may be in the wrong directory on the server, there could be a namespace collision between the class that the stub substitutes for and some other class, or the client could have requested the wrong URL.
Something unforeseen happened. This is a catchall that occurs only in bizarre situations.
The host cannot be found. This is very similar to The This field may contain the actual exception thrown on the server side, so it gives you further information about what went wrong. For example:
In Java 1.4 and later, use the standard java.rmi
package contains the classes that are seen by clients (objects that invoke remote methods). Both clients and servers should import java.rmi
. While servers need a lot more infrastructure than is present in this package, java.rmi
is all clients need. This package contains one interface, three classes, and a handful of exceptions.
The Remote Interface
Remote
interface tags objects as remote objects. It doesn't declare any methods; remote objects usually implement a subclass of Remote
that does declare some methods. The methods that are declared in the interface are the methods that can be invoked remotely. Example 18-8 is a database interface that declares a single method, SQLQuery( )
, which accepts a String
and returns a String
array. A class that implements this interface would include the code to send an SQL query to a database and return the result as a String
array.
Example 18-8. A database interface
import java.rmi.*;
public interface SQL extends Remote {
public String[] SQLQuery(String query) throws RemoteException;
}
SQLImpl
class that implemented the SQL
interface would probably have more methods, some of which might be public. However, only the SQLQuery()
method can be invoked by a client. Because the Remote
interface is not a class, a single object can implement multiple Remote
subinterfaces. In this case, any method declared in any Remote
interface can be invoked by a client.The Naming Class
java.rmi.Naming
class talks to a registry running on the server in order to map URLs like rmi://login.ibiblio.org/myRemoteObject to particular remote objects on particular hosts. You can think of a registry as a DNS for remote objects. Each entry in the registry has a name and an object reference. Clients give the name (via a URL) and get back a reference to the remote object. As you've seen, an rmi URL looks exactly like an http URL except that the scheme is rmi
instead of http
. Furthermore, the path part of the URL is an arbitrary name that the server has bound to a particular remote object, not a filename. The biggest deficiency of Naming
is that for security reasons (avoiding man-in-the-middle attacks), it has to run on the same server as the remote objects. It cannot register multiple objects on several different servers. If this is too restrictive, a Java Naming and Directory Interface (JNDI) context can add an additional layer of indirection so that multiple RMI registries can be presented through a single directory. Clients need only know the address of the main JNDI directory. They do not need to know the addresses of all the individual RMI registries the JNDI context is proxying for. The Naming
class has five public methods: list( )
, to list all the names bound in the registry; lookup( )
, to find a specific remote object given its URL; bind( )
, to bind a name to a specific remote object; rebind( )
, to bind a name to a different remote object; and unbind( )
, to remove a name from the registry. Let's look at these methods in turn.
public static String[] list(String url) throws RemoteException, MalformedURLException
list( )
method returns an array of strings, one for each URL that is currently bound. The url
argument is the URL of the Naming
registry to query. Only the protocol, host, and port are used. The path part of the URL is ignored. list( )
throws a MalformedURLException
if url
is not a valid rmi URL. A RemoteException
is thrown if anything else goes wrong, such as the registry's not being reachable or refusing to supply the requested information. Example 18-9 is a simple program that lists all the names currently bound in a particular registry. It's sometimes useful when debugging RMI problems. It allows you to determine whether the names you're using are the names the server expects.
Example 18-9. RegistryLister
import java.rmi.*;
public class RegistryLister {
public static void main(String[] args) {
int port = 1099;
if (args.length == 0) {
System.err.println("Usage: java RegistryLister host port");
return;
}
String host = args[0];
if (args.length > 1) {
try {
port = Integer.parseInt(args[1]);
if (port <1 || port > 65535) port = 1099;
}
catch (NumberFormatException ex) {}
}
String url = "rmi://" + host + ":" + port + "/";
try {
String[] remoteObjects = Naming.list(url);
for (int i = 0; i < remoteObjects.length; i++) {
System.out.println(remoteObjects[i]); }
}
catch (RemoteException ex) {
System.err.println(ex);
}
catch (java.net.MalformedURLException ex) {
System.err.println(ex);
}
}
}
% java RegistryLister login.ibiblio.org
rmi://login.ibiblio.org:1099/fibonacci rmi://login.ibiblio.org:1099/hello
//login.ibiblio.org:1099/fibonacci
//login.ibiblio.org:1099/hello
public static Remote lookup(String url) throws RemoteException, NotBoundException, AccessException, MalformedURLException
lookup()
method to retrieve the remote object associated with the file portion of the name; so, given the URL rmi://login.ibiblio.org:2001/myRemoteObject, it would return the object bound to myRemoteObject
from login.ibiblio.org on port 2,001. This method throws a NotBoundException
if the remote server does not recognize the name. It throws a RemoteException
if the remote registry can't be reached; for instance, because the network is down or because no registry service is running on the specified port. An AccessException
is thrown if the server refuses to look up the name for the particular host. Finally, if the URL is not a proper rmi URL, it throws a MalformedURLException
.
public static void bind(String url, Remote object) throws RemoteException, AlreadyBoundException, MalformedURLException, AccessException
bind( )
method to link a name like myRemoteObject
to a remote object. If the binding is successful, clients will be able to retrieve the remote object stub from the registry using a URL like rmi://login.ibiblio.org:2001/myRemoteObject. Many things can go wrong with the binding process. bind()
throws a MalformedURLException
if url
is not a valid rmi URL. It throws a RemoteException
if the registry cannot be reached. It throws an AccessException
, a subclass of RemoteException
, if the client is not allowed to bind objects in this registry. If the URL is already bound to a local object, it throws an AlreadyBoundException
.
public static void unbind(String url) throws RemoteException, NotBoundException, AlreadyBoundException, MalformedURLException, AccessException // Java 1.2
unbind( )
method removes the object with the given URL from the registry. It's the opposite of the bind( )
method. What bind( )
has bound, unbind( )
releases. unbind( )
throws a NotBoundException
if url
was not bound to an object in the first place. Otherwise, this method can throw the same exceptions for the same reasons as bind()
.
public static void rebind(String url, Remote object) throws RemoteException, AccessException, MalformedURLException
rebind( )
method is just like the bind( )
method, except that it binds the URL to the object, even if the URL is already bound. If the URL is already bound to an object, the old binding is lost. Thus, this method does not throw an AlreadyBoundException
. It can still throw RemoteException
, AccessException
, or MalformedURLException
, which have the same meanings as they do when thrown by bind()
.The RMISecurityManager Class
SecurityManager
object in place. (Like other classes, stub classes can always be loaded from the local class path.) For applets, the standard AppletSecurityManager
fills this need. apps can use the RMISecurityManager
class to protect themselves from miscreant stubs:
public class RMISecurityManager extends SecurityManager
RMISecurityManager
doesn't allow even that, and this class is so restrictive, it's essentially useless. In the Java 1.5 documentation, Sun finally admitted the problem: "RMISecurityManager
implements a policy that is no different than the policy implemented by SecurityManager
. Therefore an RMI app should use the SecurityManager
class or another app-specific SecurityManager
implementation instead of this class."Remote Exceptions
java.rmi
package defines 16 exceptions, listed in Table 18-1. Most extend java.rmi.RemoteException
. java.rmi.RemoteException
extends java.io.IOException
. AlreadyBoundException
and NotBoundException
extend java.lang.Exception
. Thus, all are checked exceptions that must be enclosed in a try
block or declared in a throws
clause. There's also one runtime exception, RMISecurityException
, a subclass of SecurityException
. Remote methods depend on many things that are not under your control: for example, the state of the network and other necessary services such as DNS. Therefore, any remote method can fail: there's no guarantee that the network won't be down when the method is called. Consequently, all remote methods must be declared to throw the generic RemoteException
and all calls to remote methods should be wrapped in a try
block. When you just want to get a program working, it's simplest to catch RemoteException
:
try {
// call remote methods...
}
catch (RemoteException ex) {
System.err.println(ex);
}
Table 18-1. Remote exceptions
AccessException
AlreadyBoundException
ConnectException
ConnectIOException
MarshalException
UnmarshalException
NoSuchObjectException
NotBoundException
RemoteException
ServerError
ServerException
RemoteException
was thrown while the remote method was executing.
StubNotFoundException
UnexpectedException
UnknownHostException
java.net.UnknownHostException
.RemoteException
class contains a single public field called detail
:
public Throwable detail
try {
// call remote methods...
}
catch (RemoteException ex) {
System.err.println(ex.detail);
ex.detail.printStackTrace( );
}
getCause()
method to return the nested exception instead:
try {
// call remote methods...
}
catch (RemoteException ex) {
System.err.println(ex.getCause( ));
ex.getCause( ).printStackTrace( );
}