server to handle multiple requests simultaneously threading; or automatically starting and shutting down servers, which allows the server lifecycle to conserve resources.
In any distributed application, programmers need to write the first and second types of code; if they did not need to write the business logic, the application wouldnt be necessary. Similarly, the
user interface, which enables users to access the business logic, needs to be written for any application. And the fifth type, code that enables the application to scale, can be the most difficult
and application-specific code to write.
The third and fourth types of code, however, are different. Most of this code can be automatically generated without much programmer thought or effort.
[ 2]
It may seem difficult to write marshalling code if youve never done so before. However, by the second time, its easy. By the third time,
most programmers are flat-out bored by the task.
[ 2]
As a corollary, it ought to be generated automatically. Code that bores the programmer is code that is likely to contain errors.
We will see in this chapter that RMI either already contains™or will automatically generate™most of the code in the third and fourth categories. Indeed, this alone is a compelling reason to use
RMI.
[ 3] [ 3]
Or a similar object-distribution framework such as CORBA.
4.1.1 Methods Across the Wire
Though convenient, automatically generating marshalling and demarshalling code is mostly a side effect produced in the service of a much more important goal. In a nutshell:
RMI is designed to make communication between two Java programs, running in separate JVMs, as much like making a method call inside a single process as
possible.
This is an ambitious goal. How does RMI achieve it? Recall that in order to communicate with the printer server, we wrote an object,
ClientNetworkWrapper , which did three things:
• It opened a socket.
• It told an instance of
DocumentDescription to write itself to the stream.
• It read and interpreted information from the input stream associated with the socket.
In addition, we wrote a companion object, ServerNetworkWrapper
, which played an analogous role on the server side.
RMI relies on two similar types of objects that are automatically generated by the RMI Compiler from an implementation of the server: stubs and skeletons. A stub is a client-side object that
represents a single server object inside the clients JVM. It implements the same methods as the server object, maintains a socket connection to the server objects JVM automatically and is
responsible for marshalling and demarshalling data on the client side. A skeleton is a server-side object responsible for maintaining network connections and marshalling and demarshalling data
on the server side.
The word stub is actually used to mean two different things. Depending on context, it might refer to a stub class that is
automatically generated from a server class object.
automatically generated from a server class object. Alternatively, it might refer to an instance of a particular stub
class that is, to a reference to a specific instance of the server class. Because stubs have such a well-defined role in
a distributed architecture, the meaning is usually clear from context. Similarly, skeleton can either refer to the skeleton
class or to an instance of the skeleton class.
The basic procedure a client uses to communicate with a server is as follows: 1. The client obtains an instance of the stub class. The stub class is automatically
pregenerated from the target server class and implements all the methods that the server class implements.
2. The client calls a method on the stub. The method call is actually the same method call the client would make on the server object if both objects resided in the same JVM.
3. Internally, the stub either creates a socket connection to the skeleton on the server or reuses a pre-existing connection. It marshalls all the information associated to the
method call, including the name of the method and the arguments, and sends this information over the socket connection to the skeleton.
4. The skeleton demarshalls the data and makes the method call on the actual server object. It gets a return value back from the actual server object, marshalls the return
value, and sends it over the wire to the stub. 5. The stub demarshalls the return value and returns it to the client code.
Stubs and skeletons are shown in Figur e 4- 1
.
Figure 4-1. A basic RMI call with a stub and skeleton
If this approach seems familiar, its because the stub and the skeleton are really automatically generated, object-oriented versions of the objects we created for our socket-based printer server.
rmic: The RMI Compiler
Stubs and skeletons are generated from server class files by a command-line application called
rmic . This application ships with Suns
version of the Java Development Kit JDK. The simplest invocation of rmic
uses the following format: rmic [full class name including packages]
For example: rmic com.ora.rmibook.chapter4.printers.NullPrinter
Assuming the class is on your classpath, this will generate two additional class files in the same directory as the original class file. The names of
the generated classes will be the original class name, appended with
_Skel and
_Stub . Thus, our example will generate the following two
classes: com.ora.rmibook.chapter4.NullPrinter_Skel
com.ora.rmibook.chapter4.NullPrinter_Stub The most commonly used flag with
rmic is
-keep , it automatically
generates the Java source code used for the stub and skeleton classes, which well see later.
Lets take a close look at this. Here is part of the stub generated for our NullPrinter
class: public final class NullPrinter_Stub extends java.rmi.server.RemoteStub
implements com.ora.rmibook.chapter4..Printer, java.rmi.Remote {
... methods from remote interfaces
implementation of printDocumentDocumentDescription
public boolean printDocumentcom.ora.rmibook.chapter4.DocumentDescription param
DocumentDescription_1 throws om.ora.rmibook.chapter4.PrinterException,
java.rmi.RemoteException {
try { ...
java.rmi.server.RemoteCall call = ref.newCalljava.rmi.server.
RemoteObject this, operations, 0, interfaceHash; try {
java.io.ObjectOutput out = call.getOutputStream ;
out.writeObjectparam_DocumentDescription_1; }
catch java.io.IOException e { throw new
java.rmi.MarshalExceptionerror marshalling arguments, e;
} ref.invokecall;
boolean result; try {
java.io.ObjectInput in = call.getInputStream ;
result = in.readBoolean ; }
catch java.io.IOException e { throw new
java.rmi.UnmarshalExceptionerror unmarshalling return, e;
} finally {
ref.donecall; }
return result; While this may seem a bit more complex than the code we wrote for the socket-based printer
server and the fact that were showing only part of the code indicates that stubs are actually quite a bit more complicated than the
ClientNetworkWrapper class might have led us to expect,
the fact remains: the stub implements the Printer
interface, and the implementation of each method in the
Printer interface simply pushes data onto a stream, and then reads data from a
stream.
Strictly speaking, skeletons arent really necessary. They can be replaced by a more generic framework that uses Javas
reflection API to invoke methods on the server side. Well cover this in more detail in
Chapt er 8 . In this book, however,
our code uses skeletons.
4.1.2 Passing by Value Versus Passing by Reference