3.6. Remote Object ActivationAutomatic activation of remote objects is a new feature in RMI as of Java 1.2. The activation subsystem in RMI provides you with two basic features: the ability to have remote objects instantiated (activated) on-demand by client requests, and the ability for remote object references to remain valid across server crashes, making the references persistent. These features can be quite useful in certain types of distributed applications. For example, think back to the AccountManager class we discussed when we talked about factory objects. We might not want to keep the AccountManager running on our server 24 hours a day; perhaps it consumes lots of server resources (memory, database connections, etc.), so we don't want it running unless it is being used. Using the RMI activation service, we can set up the AccountManager so that it doesn't start running until the first client requests an Account. In addition, after some period of inactivity, we can have the AccountManager shut down to conserve server resources and then reactivated the next time a client asks for an Account. If a remote object is made activatable, it can be registered with the RMI registry without actually being instantiated. Normally, RMI remote objects (based on the UnicastRemoteObject interface) provide only nonpersistent references to themselves. Such a reference can be created for a client only if the referenced object already exists in a remote Java VM. In addition, the remote reference is valid only during the lifetime of the remote object. The remote object activation service adds support for persistent remote references that can be created even if the remote object is not running at the time of the request and that can persist beyond the lifetime of an individual server object. The key features provided by the RMI activation service include:
In the RMI activation system, activatable objects belong to activation groups, and each activation group runs within its own Java VM. If you don't group your activatable objects, simply assigning a new activation group to each activatable object you create, then each object runs inside a separate Java VM. You typically define an activatable remote object by:
If you want remote clients to directly access your activatable object, you also need to register the object with the RMI registry, so that it can be found by name on the network. You can register an activatable class with the registry without actually creating an instance of the remote object, as we'll see shortly. You can also create an activatable object without subclassing the Activatable class. This might be necessary if you need to extend another class and the Java single-inheritance limit keeps you from also extending Activatable. For most of this section, we'll just discuss the case where you're subclassing Activatable; I'll only mention this other approach when needed. 3.6.1. Persistent Remote ReferencesThe primary difference between an activatable remote object and a nonactivatable one is that a remote reference to an activatable object doesn't need to have a "live" object behind it. If an activatable object is not running (e.g., it hasn't been constructed yet, or it has been garbage-collected by its Java VM, or its VM has exited), a remote reference to the object can still be exported to a client. The client receives a stub, as usual, and can make remote method invocations through the stub. When the first method is invoked, the activation service running on the server sees that the object is not active and goes about activating the object for the client. If the object doesn't have a VM to run in, the activation system starts one. The object is then activated using information that has been registered with the activation system. This information includes the object's class name, a URL that can load the class bytecodes if they're not found in the local CLASSPATH, and data to pass into the object's activation constructor. Once the object has been activated, the method invocation takes place, and the results are marshaled and sent back to the client. As long as the object stays running, future method requests are handled as usual. If the object stops running for some reason (e.g, it is garbage-collected, or its VM dies), the next method request triggers the activation service again, and the object is reactivated. This is what is meant by persistent remote references: remote references to activatable objects can persist across multiple lifetimes of the actual server object. 3.6.2. Defining an Activatable Remote ObjectNaturally, before you can register and use an activatable object with the RMI activation system, you need to define the remote interface and the server implementation for the object. The java.rmi.activation package provides the classes you need to define an activatable remote object. You usually define a remote object as activatable by subclassing it from Activatable and defining a special constructor that activates the object. You also have to register the object with the activation service on the server host. Other than that, the implementation of an activatable remote object is similar to that of a nonactivatable one. You start with a remote interface that contains the methods you want to export from your object. The interface should extend Remote, and each method should throw a RemoteException (or, as of Java 1.2, any parent of RemoteException). The server implementation implements this interface and extends a concrete implementation of the java.rmi.server.RemoteServer class. Since you're defining an activatable remote object, you typically extend java.rmi.activation.Activatable directly and use its constructors to initialize, register, and activate your remote object. If you choose not to extend Activatable directly, you have to use the static exportObject() methods on the Activatable class to register your object with the activation runtime system. 3.6.2.1. The Activatable classThe Activatable class has four constructors. Here are signatures for two of them: protected Activatable(String src, MarshalledObject data, boolean restart, int port) throws RemoteException protected Activatable(String src, MarshalledObject data, boolean restart, int port, RMIClientSocketFactory csfactory, RMIServerSocketFactory ssfactory) throws RemoteException These two constructors are initialization constructors. You use them when you decide to proactively create one of your remote objects and register it with the RMI activation service. In this case, the object already exists when a client first makes a method request on it, but if the object is destroyed, the next client request causes the object to be reactivated. These constructors register an object with the local activation service and export the object so that it can receive remote method requests. Both constructors have the following arguments in common:
The second initialization constructor takes custom client and server socket factories that create socket communications between the server and the clients of the object. Customized socket factories are a new feature in RMI as of the Java 2 SDK 1.2. I won't discuss them in this chapter, but you can consult the RMI API reference in Part 3, "API Quick Reference" for more details. The other two Activatable constructors have the following signatures: protected Activatable(ActivationID id, int port) throws RemoteException protected Activatable(ActivationID id, int port, RMIClientSocketFactory csfactory, RMIServerSocketFactory ssfactory) throws RemoteException These constructors are (re)activation constructors. The activation system uses them to activate a remote object that has received a remote method request, but isn't currently active. The ActivationID is a persistent ID issued by the activation system for the remote object, and the port number is the port that exports the remote object. The second constructor again takes custom server and client socket factories. The Activatable class also has a set of exportObject() methods that correspond to the constructors I've just described. You can use these methods when an activatable object doesn't directly extend the Activatable class. You call the appropriate exportObject() methods from within the constructors of the class, so they serve the same function as calling the Activatable constructors during initialization of an Activatable subclass. 3.6.2.2. Implementing an activatable objectAs I already mentioned, you can implement an activatable remote object in two ways: derive the remote object from the Activatable class directly and make the required calls to the Activatable constructors in its constructors, or have the class implement a Remote interface and make the required calls to the static exportObject() methods in its constructors. In either case, when the activation system activates a remote object, it looks for a constructor on the class that takes two arguments: an ActivationID and a MarshalledObject. The activation system calls this constructor, passing in an ActivationID it generates for the object and the MarshalledObject registered for the activatable object by the first constructor we just discussed. This means you have to provide a constructor with this signature in your implementation of an activatable object. In this constructor, you should call either one of the (re)activation constructors on the Activatable parent class (if your class extends Activatable), or the corresponding Activatable.exportObject() method (if you didn't extend Activatable). In this call, you pass on the ActivationID issued by the activation system and you specify the port for the exported remote object (a port number of 0 causes the object to be exported on a random open port). In addition to this required constructor, you can define other constructors for your remote object implementation as needed. If you want your object to be reactivatable, any additional constructors should call one of the initialization constructors on Activatable (using super()) or the corresponding exportObject() method, passing in a valid source URL and a MarshalledObject to be used as an argument if the object is reactivated. If the object is destroyed at some point, and a subsequent remote method request is received for it, the activation system reactivates the object by calling the required (re)activation constructor on the object's class, passing in this MarshalledObject argument. Example 3-5 shows an activatable implementation of the ThisOrThatServer interface from Example 3-3. The primary differences between this implementation and the nonactivatable one in Example 3-4 are that this new implementation extends the java.rmi.activation.Activatable class instead of UnicastRemoteObject, and its constructors support the activation system. This implementation also includes a name that identifies the server. Example 3-5. An Activatable Version of the ThisOrThatServerimport java.rmi.activation.*; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import java.io.IOException; public class ActivatableThisOrThatServerImpl extends Activatable implements ThisOrThatServer { // Name for server private String myName = ""; // "Regular" constructor used to create a "pre-activated" server public ActivatableThisOrThatServerImpl(String name, String src, int port) throws RemoteException, ActivationException, IOException { // Register and export object (on random open port) super(src, new MarshalledObject(name), false, port); // Save name myName = name; System.out.println("Initialization constructor called."); } // Constructor called by the activation runtime to (re)activate // and export the server protected ActivatableThisOrThatServerImpl(ActivationID id, MarshalledObject arg) throws RemoteException { // Export this object with the given activation id, on random port super(id, 0); System.out.println("Activating a server"); // Check incoming data passed in with activation request try { Object oarg = arg.get(); if (oarg instanceof String) { myName = (String)oarg; } else { System.out.println("Unknown argument received on activation: " + oarg); } } catch(Exception e) { System.out.println("Error retrieving argument to activation"); } System.out.println("(Re)activation constructor called."); } // Remotely-accessible methods public String doThis(String todo) throws RemoteException { String result = doSomething("this", todo); return result; } public String doThat(String todo) throws RemoteException { String result = doSomething("that", todo); return result; } // Non-remote methods private String doSomething(String what, String todo) { String result = myName + ": " + what + " " + todo + " is done."; return result; } } The first constructor for ActivatableThisOrThatServerImpl is a public one, used to construct a server with a given name. The constructor registers the new object with the activation system, passing in a URL that acts as a codebase for finding the classes required for this class. It also passes in the name given to the server, wrapped in a MarshalledObject. This ensures that the server is given the same name if it needs to be reactivated later. The second constructor is the required one used by the activation system. If an object of this type needs to be activated (or reactivated after a crash of some sort), this constructor is called to create the remote object. The constructor takes an ActivationID, issued by the activation system, and the MarshalledObject registered for the object with the activation system. The constructor exports the object by calling the second constructor on the Activatable class, then initializes itself with the data from the MarshalledObject. 3.6.3. Registering Activatable ObjectsThere are several ways to register an activatable object with its local activation system. In each case, the activation system needs to be told how to create (or recreate) the object. The information the activation system needs to activate an object is encapsulated in the ActivationDesc class. An ActivationDesc object contains the name of the class for the remote object, a URL with the network location of the bytecodes for the class, a MarshalledObject to be used as the initialization data for the object, and the group assignment for the object. The simplest way to register an Activatable object is to create an instance of the object. In our example, we've derived our server implementation from the Activatable class, so the public constructor on the ActivatableThisOrThatServerImpl class registers the object by calling the necessary constructor on Activatable. Thus, we can create and register one of these as follows: // Make an activation group for the object ActivationGroupDesc gdesc = new ActivationGroupDesc(null, null); ActivationGroupID gid = ActivationGroup.getSystem().registerGroup(gdesc); ActivationGroup.createGroup(gid, gdesc, 0); // Make a server object, which registers it with activation system ThisOrThatServer server = new ActivatableThisOrThatServerImpl(serverName, codebaseURL, 0); // Register with naming service LocateRegistry.getRegistry().rebind(serverName, server); The first four lines are required to create an activation group for our activatable object. We'll talk more about activation groups shortly. For now, all you need to know is that this code creates the default activation group for the current VM. Any remote object that isn't specifically assigned to a group is placed in this default group. The activatable object itself is created by simply calling the public ActivatableThisOrThatServerImpl constructor. This constructor registers the object with the activation system by calling the appropriate Activatable constructor, as we've already discussed. Since we haven't specified an activation group for the object, it is placed in the default group we just created. If we hadn't created that default group, the activation system would throw an exception here, when the object is registered. Aside from the creation of the activation group, this example looks a lot like our other examples of registering RMI objects. The difference here is that if the registering process dies off at some point, the activation system can reactivate the activatable object in a new Java VM using the information provided in the ActivationDesc for the object. In this case, we're relying on the Activatable constructor (which is called by our ActivatableThisOrThatServerImpl constructor) to create and register an ActivationDesc for our object. When an object needs to be activated, the activation system first looks up the ActivationDesc for the object and then looks for the class referenced in the ActivationDesc, using the URL to load the class bytecodes. Once the class has been loaded, the activation system creates an instance of the class by calling the activation constructor, which takes an ActivationID and a MarshalledObject as arguments. The ActivationID is issued by the activation system, and the MarshalledObject contains the data previously registered with the ActivationDesc. In our activatable ThisOrThatServer in Example 3-5, the activation system calls the second constructor on our ActivatableThisOrThatServerImpl class. The new object passes the ActivationID up to the Activatable constructor so that it can be recorded, and the name of the server is pulled from the MarshalledObject. The Activatable constructor takes care of creating and registering an ActivationDesc for the object and exporting the object with the activation system. 3.6.3.1. Registering an activatable object without instantiatingA more complicated, but often more useful way to register a remote object is to create an ActivationDesc for it and then register the information directly with the activation system, without creating an instance of the object. The static Activatable.register() method accepts an ActivationDesc object and registers it with the activation system directly. Here's how we can do that: // Make a codebase and activation argument for the object String src = "http://objhost.org/classes"; MarshalledObject actArg = new MarshalledObject("MyServer"); // Create the ActivationDesc and get a stub for the object ActivationDesc desc = new ActivationDesc("ActivatableThisOrThatServerImpl", src, actArg); ThisOrThatServer serverStub = (ThisOrThatServer)Activatable.register(desc); When we create the ActivationDesc for the object, we specify the name of the class to use for creating the object, a codebase for finding the class, and a MarshalledObject that is passed to the object when it's activated. The ActivationDesc is used in the call to the Activatable.register() method, which returns a RemoteStub for the activatable object. Since we know this stub is for an object that implements the ThisOrThatServer interface, we can safely cast it to a ThisOrThatServer. We can also use this reference to register the remote object with the local RMI naming registry: LocateRegistry.getRegistry().bind("ThisOrThatServer", serverStub); Although I haven't shown it here, note that you also have to create an activation group for the object, just like we did in our earlier example, before you can register it with the activation service. So, to recap, we've registered a remote object with the activation system and the RMI naming registry without actually creating the object itself. When a client tries to look up the object, it gets back a remote stub, with no active object behind it on the server. When the client calls a method on the stub, however, the activation system on the server creates the object, using the information in the ActivationDesc we provided. 3.6.3.2. Passing data with the MarshalledObjectThe way you can pass arguments to activatable objects before they are activated is through the MarshalledObject contained within the ActivationDesc for the object. However, once the ActivationDesc is registered with the activation system, you can't dynamically update the contents of the MarshalledObject. One way to have the arguments to an activatable object be dynamic is to bundle a filename or URL into the MarshalledObject. At the point that the object is activated, it can read data from the file or URL and use that data during activation. 3.6.4. Activation GroupsEvery activatable RMI object belongs to an activation group. Each group of activatable objects runs within the same Java VM on the server host. In essence, activation groups are a way of defining collections of activatable remote objects that should share the same physical address space. We've already seen how to set up an activation group, since we had to do this before registering our activatable object with the activation system. In this section, we'll take a look at creating activation groups in a bit more detail and discuss what the activation group is actually doing for you. Activation groups in RMI are more than just a way of organizing remote objects. Each activation group is responsible for monitoring, activating, and reactivating the objects it contains. The objects involved in maintaining an activation group are shown in Figure 3-4. Note that you don't normally need to interact with the underlying objects themselves. You simply set up your ActivationGroup objects and assign activatable objects to them; the activation system does the rest for you. Figure 3-4. The components of the activation systemAn ActivationGroup is created when the first object in the group needs to be activated. The Activator is responsible for creating a VM for the ActivationGroup to run in, and for starting the ActivationGroup using the information in the registered object's ActivationGroupDesc, if it has one. If the remote object doesn't have a specified group, a default one is created. The new ActivationGroup object is then told to activate the requested remote object, by calling its newInstance() method. The arguments the Activator passes into this method are the ActivationID for the new object and the ActivationDesc that the Activator has registered for the object. The ActivationDesc gives an ActivationGroup everything it needs to activate the remote object. The ActivationGroup takes the class name for the object and looks for the class bytecodes. First it checks the local CLASSPATH, and if that pulls up nothing, it uses the URL in the ActivationDesc to load the class from the given URL. Once the class is loaded, an instance of the class is created by calling the activation constructor on the class (e.g., the constructor that has an ActivationID argument and a MarshalledObject argument). The ActivationID and MarshalledObject come from the call to the newInstance() method. The new, active remote object is returned to the Activator as a serialized MarshalledObject. This is done for two reasons. First, the Activator runs in a separate Java VM, so the active object reference needs to be transferred from one VM to another, and the easiest way to do this is to serialize it and transmit it in that form. Second, since the object has been bundled into a MarshalledObject, the Activator doesn't need to load the object's bytecodes unless absolutely necessary. In most cases, the Activator doesn't need to interact directly with the object itself, so it doesn't need to waste time loading unnecessary bytecodes. Each ActivationGroup has an ActivationMonitor associated with it. The ActivationGroup has to tell the ActivationMonitor whenever an object becomes active or inactive. An activatable object is responsible for informing its ActivationGroup when it becomes active and inactive, by calling the group's activeObject() and inactiveObject() methods, respectively. The ActivationGroup, in turn, passes the information on to the ActivationMonitor by calling identical methods on the monitor object. When the object becomes inactive, the ActivationMonitor makes note of it and arranges for the object to be reactivated the next time a method request comes in for it. If an entire ActivationGroup becomes inactive, the ActivationMonitor is informed through its inactiveGroup() method. The next request for an object in that group causes the Activator to recreate the group. 3.6.4.1. Registering activation groupsAn ActivationGroup is registered with the activation system in roughly the same way as an activatable object. You have to create an ActivationGroupDesc object that contains the name of the class for the group, the URL where the class bytecodes can be loaded, and a MarshalledObject that is given to the ActivationGroup as initialization data. Unlike activatable objects, though, the class of a group has to be a concrete subclass of ActivationGroup. You register the ActivationGroupDesc by calling the static ActivationSystem.registerGroup() method, passing in the ActivationGroupDesc. The ActivationSystem returns an ActivationGroupID that can assign specific objects to the group. 3.6.4.2. Assigning activatable objects to groupsYou assign an activatable object to a group by specifying the group ID in the ActivationDesc registered with the activation system. The ActivationGroupID returned by the ActivationSystem.registerGroup() method can be passed into the ActivationDesc constructor. Before you can register a remote object with the activation system, you need to create a group for it. For our activatable ThisOrThatServer example, we can run Java code along the following lines on the object server (note I've left out the exception handling): // Make an activation group for the object ActivationGroupDesc gdesc = new ActivationGroupDesc(null, null); ActivationGroupID gid = ActivationGroup.getSystem().registerGroup(gdesc); ActivationGroup.createGroup(gid, gdesc, 0); // Set up ActivationDesc for object String codebaseURL = "http://objhost.org/classes"; String serverName = "Fred"; MarshalledObject activationArg = new MarshalledObject(serverName); ActivationDesc desc = new ActivationDesc(gid, "ActivatableThisOrThatServerImpl", codebaseURL, activationArg); ThisOrThatServer serverRef = (ThisOrThatServer)Activatable.register(desc); LocateRegistry.getRegistry().rebind(serverName, serverRef); Here we're using the ActivatableThisOrThatServerImpl class and registering a remote object with the activation system without actually instantiating it. Before we register our remote object, we create an ActivationGroupDesc, then use it to register and create a new activation group with the activation system. After we create the activation group (using the ActivationGroup.createGroup() method), we use the ActivationGroupID for our new group to make an ActivationDesc for our remote object, and we use that to register the object with the activation system. The activation system generates a remote stub for our object, and we register that with the RMI naming registry. Since each ActivationGroup is started within its own VM if it's initially activated by the activation system, grouping objects is a convenient way to partition your remote objects into shared address spaces on your server. For more details on the activation group interfaces in RMI, consult the java.rmi.activation reference material in Chapter 14, The java.rmi.activation Package. 3.6.5. The Activation DaemonThe heart of the RMI activation system is the activation daemon, which runs on the host for an activatable object. The activation daemon is responsible for intercepting remote method requests on activatable objects and orchestrating the activation of the object, if needed. The activation daemon provided with the Java SDK, rmid, runs a Java VM that includes a java.rmi.activation.Activator object. The Activator is responsible for keeping a registry of activatable objects, along with the information needed to activate them. This information is in two parts: an ActivationDesc object and an optional ActivationGroupDesc. The ActivationGroupDesc identifies the group of activatable objects to which the object should be added and describes how to start the group if it doesn't exist. The ActivationDesc includes all information needed to activate the object itself. An activatable object has to be registered nwith the activation system in one of the ways described earlier to be started automatically by the Activator. If a remote method request is received by the RMI runtime system on a host, and the target object hasn't been created yet, the Activator is asked to activate it. The Activator looks up the ActivationDesc (and ActivationGroupDesc, if present) for the object. If the object has an ActivationGroup assigned to it, and the ActivationGroup doesn't exist yet, a Java VM is started for the group, and the ActivationGroupDesc data is used to start an ActivationGroup object within the new VM. If the object has no ActivationGroup associated with it, it's given its own ActivationGroup running in its own VM. The group is then asked to start the requested object, using the ActivationDesc object registered for the object. Once the ActivationGroup activates the object within its VM, the Activator is notified, and the now-active remote reference is returned to the RMI runtime system. The RMI runtime system forwards the remote method request through the reference to the object, and the return value is exported back to the client as usual. 3.6.5.1. The daemon's dual personalityWhen you start the rmid daemon, it creates an Activator and then listens on the default port of 1098 for activation requests. There is also a -port command-line option that lets you specify a different port for the VM to use. In addition to running the Activator, the rmid daemon also runs its own RMI Registry. If needed, you can register local objects with the daemon's internal Registry by specifying the daemon's port when you call the bind() or rebind() method of the Registry. For example, if rmid is running on its default port of 1098: RemoteObject server = ... Registry local = LocateRegistry.getRegistry(1098); local.bind(server, "Server"); This way, you can consolidate your activation system and your naming service into one VM on your server. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|