Revolutionary RMI: Dynamic class loading and behavior objects

Find out how RMI can help you define extensible, distributed object-oriented frameworks

Welcome to Enterprise Java, a new column dedicated to the Java Enterprise APIs: RMI, Java IDL, JDBC, Servlets, and EJB. I originally intended to call this column "Java Enterprise Jumpgate," in reference to the jumpgate technology in the hit TV series Babylon 5. (A jumpgate allows you to move between known points in the universe for trade, exploration, and laying waste -- which is similar to the purpose of the Internet.) I feel the analogy to distributed programming fits: the goal of this column is to demonstrate the capabilities, strengths, and weaknesses of each distributed programming API, helping you jump to new programming destinations. But you know editors, they just won't let you have any fun at their magazine's expense!

A word to the wise: distributed programming is inherently more difficult than standalone programming. However, the Java Enterprise APIs give Java programmers tools to ease the burden of producing robust, efficient applications. This column will help you get production-level, industrial-strength applications out the door, on time and on target.

When it comes to distributed computing, no area is getting more interest today from programmers than the Remote Method Invocation (RMI) API. Programmers love it, Microsoft hates it, and vendors want to make money off of it. EJB, Jini, and JavaSpaces all have their roots in RMI. So what better subject to kick off this column.

Getting excited about RMI

As an instructor, no Java Enterprise API is more rewarding to teach than RMI. It always works the same way:

  • Introduction
  • RMI hello world
  • Callbacks
  • Object serialization and RMI

The student reactions follow the modules:

  • Come on, caffeine, work!
  • Not "hello world" again...
  • Finally, something I can use
  • Wow, cool! Can we do that again?

Object serialization and RMI puts a smile on programmers' faces because they witness the subtle strength of RMI: the ability to transport true objects between processes.

What excites students is an example that demonstrates behavior objects. A behavior object is an instance that implements an interface, where the interface declares a remote method parameter type. For example, consider the following interfaces:

public interface ScribbleShape {
    public void draw(java.awt.Graphics g);
}

import java.rmi.*;
public interface ScribblePad extends Remote {
    public void addShape(ScribbleShape s) throws RemoteException;
}

The ScribblePad.addShape() method parameter represents a behavior object because the parameter instance must support the ScribbleShape interface, the ScribbleShape interface doesn't extend Remote, and the parameter will be passed between processes using serialization. From the ScribblePad implementation's perspective, the addShape() method is invoked by a Java object running in a process across the network and delivers a serialized version of the ScribbleShape instance that existed in the client process. The ScribblePad implementation can store the parameter value in a Vector, invoke methods on the object, serialize it to a file, or pass to yet another RMI object on a different machine.

All the RMI object knows about that parameter value is that it implements the ScribbleShape interface, exists in its address space (not remote), and supports the draw() method. When the ScribblePad implementation is done with the object, it will be garbage collected; of course, the client's version maintains its own separate state and identity.

In a nutshell, behavior objects are quite useful: they provide a mechanism for defining extensible, distributed object-oriented frameworks. Such frameworks prove useful when implementing the following:

  • Distributed algorithms. Some parallelized versions of sorting and searching algorithms are well suited to distributed processing. The premise behind implementing these is the presence of heterogeneous systems residing on the network. That is, after all, Java's claim to fame -- placing a homogeneous wrapper around heterogeneous networked systems.

  • User interfaces to networked services. Ever think about how a smart home would actually be configured? How do you go about controlling all the devices in a manufacturing plant (vision systems, robots, automated testing equipment, and environmental systems)? One method for solving this problem is to remotely load objects from the device that subclasses Panel, which presents controls to configure the device. The remotely loaded panels allow the user to fully customize a networked device without requiring installation of device drivers or special libraries.

  • Performing tasks within remote servers without being connected or polling. Just think if you could upload your stock object into an online brokerage server and it could monitor continuously, changing stock prices, news, indices, and whatever other parameters you would like, and the stock purchase object would autonomously buy and sell stocks without you having to do a thing. Send in your object and forget about it, no more staying up late to see how the Asian markets did!

  • Games. Some online games enable players to add weapons that have new powers and behaviors, like autonomously guarding areas or performing sophisticated search patterns. Behavior objects provide the capability to pass these objects into an opponent's space and wreak havoc until the garbage is collected.

RMI class loading

After contemplating the idea of behavior objects, you may start mumbling to yourself: What if the remote object can't locally load the necessary classes to deserialize the behavior object? Well now, that is the question when it comes to RMI. But the answer requires a little under-the-covers RMI, concerning how RMI loads classes when dealing with RMI methods.

From RMI's perspective, class files come in two flavors: code directly used by a program, and everything else. All classes a program compiles to are considered code directly used; this includes all core Java classes along with classes and interfaces used to declare parameters, return types, and variable declarations. Code considered not directly used includes stub and skeleton classes and extended classes of parameters.

When servicing a remote method call, RMI attempts to load marshalled values using one of three class loaders:

  • AppletClassLoader, if code is running within an applet

  • The system default class loader, if all classes are directly used by an application

  • RMIClassLoader to load classes not directly used by the application

So, here's the test:

  1. What class loader is used to load core Java classes (String, Thread, Component)? Answer: the system default class loader.

  2. What class loader is used to load the ScribbleShape interface when the ScribblePad.addShape() method is invoked by a remote process? Answer: the system default class loader.

  3. What class loader is used to load an implementation of ScribbleShape passed as a parameter to ScribblePad.addShape()? Answer: the RMIClassLoader, because the parameter instance's class is not directly used by the application.

Congratulations, you all get an A for the day. So where does the RMIClassLoader find the missing class files? Why, from the annotated codebase URL, of course.

The annotated what?

Dynamic class loading

RMI uses serialization to marshall remote method parameters and return values. Specifically, it serializes two values for each parameter: a String representation of a URL from which to remotely load the method value's class, and the serialized value. The act of writing the URL onto the stream is known as annotating the codebase URL.

The codebase URL is determined in one of the following ways:

  • If the class was loaded by a class loader other than the system's class loader, the class loader codebase is annotated

  • The value of java.rmi.server.codebase system property

  • If the codebase system property is not defined, then the codebase URL is null

Thus,

  • All objects created within an applet are annotated with the applet's codebase

  • Objects created within a typical application are annotated with the java.rmi.server.codebase system property

  • Behavior objects created on another machine and passed via an RMI call are loaded from the codebase host from which the object was originally instantiated

So, here's an interesting question: What if the remote object doesn't possess the necessary classes to deserialize the behavior object? RMI will load the classes from the codebase of the invoking process.

But, wait! That's not all. The Java virtual machine requires a security manager be installed to enable remote class loading. For applets, this is a nonissue because a security manager is ever present within Web browsers. Applications require the manual installation of a security manager. The most commonly used security manager to enable dynamic class loading is the RMISecurityManager. Applications usually install a security manager before performing any other functions:

public static void main(String args[]) {
   System.setSecurityManager(new RMISecurityManager());
   //other application tasks
} 

With the introduction of JDK 1.2, you must provide a security policy file when using the RMISecurityManager. Assuming you are on a Windows machine, you can modify the system policy file at java_home\jre\lib\security\java.policy. Alternatively, you can specify a policy file on the command line by setting the java.security.policy system property. That's my approach for this article. The policy file contains:

grant {
    // Allow everything for now
    permission java.security.AllPermission;
};

For more information on configuring policy files, see the JDK 1.2 security link in the Resources.

An example program

A distributed scribble application helps to demonstrate dynamic class loading and behavior objects. I chose this program because it's simple without being trivial, and it provides a visual means to demonstrate this nonvisual technology. The scribble application enables multiple clients to simultaneously present and manipulate a drawing. This is accomplished by maintaining a list of shapes in a central RMI object.

The scribble application is composed of three separate programs:

  • Scribble Pad -- A scribble editor program that enables the user to add new shapes to the drawing.

  • Scribble Viewer -- A client scribble program that provides viewing capabilities but no editing. capabilities.

  • Scribble Server -- The central RMI server application that maintains the list of shapes and registered clients. A client program adds a shape to the drawing by sending a ScribbleShape object to the server; in turn, the server broadcasts the new shape to all registered clients through a callback.

When the server is up and running, it presents an activity log using a JTextArea, and the scribble pad clients present a JFrame window that displays the drawing. The following figures show the appearance of each program.

Figure 1: Scribble Server Log
Figure 2: Scribble Pad editor
Figure 3: Scribble Viewer

The drawing consists of a list of objects that implement the ScribbleShape interface. Thus, all shapes passed to the server for inclusion to the drawing must implement the simple interface:

interface ScribbleShape {
   public void draw(Graphics g);
}

The figures above illustrate the three implementations of the ScribbleShape interface: ScribbleLine, ScribblePoint, and ScribbleX. Each class implements the draw() method to render their characteristic appearance on a Graphics object. The simplest shape implementation is the ScribbleX:

class ScribbleX implements ScribbleShape, Serializable {
   private Point fPt;
   private Color fColor = Color.red;
   public ScribbleX(Point p1) {
      this(p1, Color.red);
   }
   public ScribbleX(Point p1, Color c) {
      fPt = p1;
      fColor = c;
   }
   public void draw(Graphics g) {
      g.setColor(fColor);
      g.drawLine(fPt.x-5, fPt.y-5, fPt.x+5, fPt.y+5);
      g.drawLine(fPt.x+5, fPt.y-5, fPt.x-5, fPt.y+5);
   }
}

Where does dynamic loading come into play? Like many applications today, the viewer is free, but there's a catch. The viewer doesn't ship with any shapes. In fact, the server costs money, but doesn't ship with any shapes, either. Users who wish to create and add shapes to the drawing must purchase a Scribble Pad, which ships with three shapes: a line, a point, and an X. The distributed scribble program relies on RMI's dynamic class loading capability to fetch the appropriate ScribbleShape implementation classes from the Scribble Pad codebase that created and added the shape(s).

The following diagram illustrates what happens when a Scribble Pad adds an X shape to the drawing.

Figure 4: Adding an X shape to the drawing with two clients

This illustration assumes all programs are currently running and connected.

Here's what happens:

1 2 3 Page 1
Page 1 of 3