The RMI runtime system has a dynamic class-loading facility that loads the classes it needs while executing remote method calls. In some situations, you don't need to worry much about how your application classes are obtained by the various agents in an RMI application. This is especially true if you have direct access to all hosts involved in the distributed system (i.e., if you can install your application classes in the local CLASSPATH for each machine participating in the application). For instance, when discussing the earlier Account example, I assumed that all the relevant classes (Account, AccountImpl, stub, and skeleton classes) were installed on both the client and the server. However, if your distributed application involves remote agents running on hosts that are not directly under your control, you need to understand how RMI loads classes at runtime, so you can ensure that each remote agent can find the classes it needs in order to run.
As with any Java application, the Java runtime system is responsible for loading the classes needed to initiate an RMI session. Starting an interaction with a remote object means loading the RMI API classes themselves, as well as the base interface for the remote object and the stub class for the remote interface. On the server side, the skeleton class for the remote object and the actual implementation class need to be loaded in order to run the server object that is being remotely exported.
The classes that are referenced directly by a given Java class are normally loaded by the same class loader that loaded the class itself. So, in an RMI client that does a Naming lookup to find a remote object, the stub interface for the remote object is loaded using the class loader for the class doing the lookup. If the RMI client is a Java application (started using the java command to invoke the main() method on an object), the default (local) class loader tries to find the remote interface locally, from the local CLASSPATH. If the RMI client is an applet loaded in a web page, the AppletClassLoader tries to look for the remote interface on the applet's host, in the codebase of the applet.
The RMI runtime system provides its own class loader, the RMIClassLoader, to augment the default class loading process I just described. The RMIClassLoader loads stubs and skeleton classes for remote interfaces, as well as the classes for objects used as remote method arguments or return values. These classes usually aren't explicitly referenced by your RMI application itself, but they are needed by the RMI runtime system for generating remote references and marshalling/unmarshalling method arguments and return values.
When it's loading the bytecodes for class definitions, the RMI runtime system first attempts to use the default class loader for the local context (i.e., an AppletClassLoader for an applet or the system class loader for a Java application). If the referenced class isn't found using the default local class loader, the RMIClassLoader tries to load the class bytecodes remotely according to the procedures explained next.
When the RMI runtime system marshals a remote object stub, method argument, or return value, it encodes a URL in the marshaled bytestream to tell the process on the receiving end of the stream where to look for the class file for the marshaled object. If the class for the object being marshaled was loaded by a nondefault class loader (e.g., the AppletClassLoader or the RMIClassLoader), the codebase of that class loader is encoded in the marshaled stream. If the class was loaded by the default class loader from the local CLASSPATH, the value of the java.rmi.server.codebase property for the Java VM marshalling the object is sent in the stream. This property is not set by default in the Java VM, so you need to make sure that it's set to a URL that points to the location of the necessary class files. One way to do this is to include a command-line argument when starting the Java VM, as in:
% java -Djava.rmi.server.codebase=http://objhost.org/classes/RMIProcess
Here we're starting a Java process with its codebase set to http://objhost.org/classes /. This means that any remote process that needs to load classes for objects received from this process during an RMI session should use this HTTP URL in order to find them (if the classes can't be found on the local CLASSPATH, that is). This applies either if RMIProcess is serving remote objects itself through an RMI registry or if RMIProcess is passing objects into methods it is calling on other remote objects. In the first case, a remote client that needs to load the stub classes for the objects exported by RMIProcess uses the codebase to find these classes. In the second case, a remote process uses the codebase to load the classes for method arguments that RMIProcess is passing into remote method calls it makes.
If an RMI runtime system is trying to unmarshal an object stub, method argument, or return value and it doesn't find the class using the default class loader (e.g., the system class loader, which looks on the local CLASSPATH first), the RMIClassLoader can use the URL in the marshal stream to look for the class bytecodes remotely. The RMIClassLoader takes the URL from the marshaled bytestream and opens a URL connection to the specified host to load the needed classes. If both the local class search and this remote URL search fail to find the required classes, the unmarshal operation generates an exception, and the remote method call fails.
Note that in order for a Java runtime system to even attempt to load classes remotely, it has to have a security manager installed that allows remote class loading. The java.rmi.RMISecurityManager can be used for this. In both your RMI object server and clients, include the following line before any RMI calls:
System.setSecurityManager(new RMISecurityManager());
If you don't set the security manager, the Java VM is allowed to look for classes only locally, and your RMI calls will work only if all of the required classes can be found on the local CLASSPATH.
Another issue with dynamically loading remote classes is that the default Java security policy doesn't allow all the networking operations required to resolve a class from a remote host. So, if you have an RMI client or server that needs to resolve classes remotely, you need to use a policy file that opens up network permissions to allow this. I'm not going to go into the details of network policies here or the syntax of the security policy file,[2] but you will need to add the following line to the policy file on the RMI client:
[2]For details on Java security policies and policy files, see Java Security, by Scott Oaks (O'Reilly).
permission java.net.SocketPermission "objhost.org", "accept,connect";
This line gives the RMI object server objhost.org the permission to open connections to the local machine. This is needed to bypass the stricter rules imposed by the RMISecurityManager. Once you've made a modified policy file, you can specify it on the command line when you start your RMI process, in a similar way to setting the codebase property:
% java -Djava.security.policy=mypolicy.txt RMIProcess
As a simple example, suppose we want to use our earlier Account example to export an Account object on one host and access that Account on another host where the only class available locally is the Account interface class itself. On the server, we start an RMI registry[3] and run the RegAccount class as before, but since we want remote clients to be able to load the stub classes remotely, we need to set the codebase property to where the clients can find these classes:
[3] Note that in order for the RMI registry to recognize and pass along the codebase property you specify, it has to be started in such a way that it can't find any of the remotely loaded classes on its CLASSPATH. So start your RMI registry with a CLASSPATH that doesn't include the stub/skeleton classes, etc., then run your RMI server with a CLASSPATH that includes all required classes.
% java -Djava.server.rmi.codebase=http://objhost.org/classes/ RegAccount Registered account.
We've setting the codebase to http://objhost.org/classes/, so we have to make sure that an HTTP server is running on the objhost.org machine and that the necessary class files (e.g., the AccountImpl stub class) are in the classes directory of that HTTP server's document root.
Now we can run the AccountClient class on the remote client as before, but the client's host machine doesn't have the stub class for the Account remote object available locally. When the AccountClient tries to look up the remote Account object, we want the stub class to be loaded remotely. Two simple changes to our Account example make this possible. First, add a line to the AccountClient main() method that sets the RMISecurityManager, in order to allow for remote class loading:
import java.rmi.Naming; import java.rmi.RMISecurityManager; public class AccountClient { public static void main(String argv[]) { try { // Set the RMI security manager, // in case we need to load remote classes System.setSecurityManager(new RMISecurityManager()); // Lookup account object Account jimAcct = (Account)Naming.lookup("rmi://objhost.org/JimF"); . . .
The other change is to use a more lenient policy file when running AccountClient so the necessary network operations can be performed. Again, I won't discuss the syntax of the policy file here, but assuming we've put the required policy settings into a file named rmipolicy.txt, we can start the client like so:
% java -Djava.security.policy=rmipolicy.txt AccountClient Deposited 12,000 into account owned by JimF Balance now totals: 12000.0
Virtually all the steps I just outlined for running an RMI client to allow it to remotely load classes apply to applets as well. The only difference is that the classes for applets are loaded using an AppletClassLoader, which checks the applet's codebase for any classes required to run the applet. The default security policy for applets already allows for remote loading of classes, since this is how an applet works in the first place, so there's no need to change the security policy when using RMI within an applet. All you need to do to ensure that the applet finds the remote interface and stub class for the RMI object is to put them in the server directory that corresponds to the applet's codebase.
Copyright © 2001 O'Reilly & Associates. All rights reserved.