Section 13.6. Dynamic Classloading


13.6. Dynamic Classloading

The RMI runtime system has a dynamic classloading 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, we assumed 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 aren't 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 classloader 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 classloader 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) classloader 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 classloader, the RMIClassLoader, to augment the default classloading process 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 marshaling/unmarshaling method arguments and return values.

When it's loading the bytecodes for class definitions, the RMI runtime system first attempts to use the default classloader for the local context (i.e., an AppletClassLoader for an applet or the system classloader for a Java application). If the referenced class isn't found using the default local classloader, the RMIClassLoader tries to load the class bytecodes remotely according to the procedures explained next.

13.6.1. Configuring Clients and Servers for Remote Classloading

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 classloader (e.g., the AppletClassLoader or the RMIClassLoader), the codebase of that classloader is encoded in the marshaled stream. If the class was loaded by the default classloader from the local CLASSPATH, the value of the java.rmi.server.codebase property for the Java VM marshaling 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 start 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 classloader (e.g., the system classloader, 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 classloading. 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 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. We don't go into all the details of network policies or the syntax of the security policy file,[2] but at minimum, you need to add the following line to the policy file on the RMI client:

[2] For details on Java code-level security policies and files, see Java Security by Scott Oaks (O'Reilly).

     permission java.net.SocketPermission "objhost.org", "accept,connect"; 

This line allows the client to connect to any port on objhost.org and to accept connections from objhost.org on any of those ports. If you want to make this a bit more restrictive, since we're using HTTP as the protocol for downloading classes, you really just need port 80 access to/from objhost.org, so use this line in the policy file instead:

     permission java.net.SocketPermission "objhost.org:80", "accept,connect"; 

The policy file change is needed in order to bypass the stricter rules imposed by the RMISecurityManager and allow the RMI runtime to create the network connections it needs. You can either add this permission to the default policy file used by your Java runtime (if you are using Sun's JRE, this is in JAVA_HOME/lib/security/java.policy, where JAVA_HOME is the root of your Java runtime installation), or you can create a custom policy file (perhaps by copying and editing the default one) and use it specifically for this application. If 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 in our earlier example:

     % 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 AccountServer 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 and skeleton classes, and then run your RMI server with a CLASSPATH that includes all required classes.

     % java -Djava.rmi.server.codebase=http://objhost.org/classes/ AccountServer     Registered account. 

We've set 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, it may seem strange that we need to provide an HTTP server in order to download classes between two RMI processes. If the RMI registry can provide remote objects to remote callers, why can't it provide class definitions as well? The reason that the designers of the RMI specification did not tie dynamic classloading to the RMI registry is because they did not want the RMI registry to be a strictly required element of the RMI architecture. In order to leave open the possibility of using other naming services to register RMI remote objects (CORBA, LDAP, custom systems), they needed to keep the RMI registry an optional component. Therefore, dynamic classloading had to be a separate, but portable, process, hence the HTTP process that exists in the specification. Luckily, as of JDK 1.4, the ability to customize the behavior of the RMIClassLoader was added to RMI, which opens up the possibility of implementing other dynamic classloading schemes (if the HTTP scheme doesn't fit your needs). See the RMI Javadoc for RMIClassLoader and RMIClassLoaderSpi (both in the java.rmi.server package) for details.

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 AccountImpl 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 classloading:

     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, we don't discuss the syntax of the policy file here, but assuming you've put the required policy settings into a file named rmipolicy.txt, you 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 

You'll notice that the -Djava.rmi.server.codebase= option isn't needed here, because the client isn't providing class definitions to the Account server. If we were passing arguments into remote methods called on the Account server, and the classes for these arguments weren't available on the client, then we would have to set up the AccountClient in a similar way to what we just described for the Account server (i.e., set up an HTTP-accessible codebase and export it to the server by setting the java.rmi.server.codebase property of the client's VM).

13.6.2. Loading Classes from Applets

Virtually all the steps we've 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.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net