One
major
benefit of RMI is that almost any kind of
object can
be passed as a parameter or return value of a remote method. The
recipient of the object will not know ahead of time the class of the
actual object it will receive. If the object is of a class that
implements Remote
(java.rmi.Remote
),
then in fact the returned object will be a proxy object that implements at
least the declared interface. If the object is not remote, it must be
serializable, and then a copy of it will
be transmitted across the Net. The prime example of this is a
String
. It makes no
sense to write an RMI proxy
object for String
. Why? Remember from Chapter 3 that String
objects are
immutable! Once you have a String
, you can copy it
locally but never change it. So Strings
, like most
other core classes, can be copied across the RMI connection just as
easily as they are copied locally. But Remote
objects will cause an RMI proxy to be delivered. So what is stopping
the caller from passing an RMI object that is also itself a proxy?
Nothing at all, and this is the basis of the powerful RMI callback
mechanism.
An RMI callback occurs when the client of one service passes an object that is the proxy for another service. The recipient can then call methods in the object it received, and be calling back (hence the name) to where it came from. Think about a stock ticker service. You write a server that runs on your desktop and notifies you when your stock moves up or down. This server is also a remote object. You then pass this server object to the stock ticker service, which remembers it and calls its methods when the stock price changes. See Figure 22-2 for the big picture.
The code for the callback service comes in several parts. Because
there are two
servers, there are
also two interfaces. The first is the
interface for the TickerServer
service. There is
only one method, connect( )
, which takes one
argument, a Client
:
package com.darwinsys.callback; import com.darwinsys.client.*; import java.rmi.*; public interface TickerServer extends java.rmi.Remote { public static final String LOOKUP_NAME = "Ticker Service"; public void connect(Client d) throws java.rmi.RemoteException; }
Client
is the interface that displays a
stock price change message on your
desktop. It also has only one method, alert( )
,
which takes a String
argument:
package com.darwinsys.client; import java.rmi.*; /** Client -- the interface for the client callback */ public interface Client extends Remote { public void alert(String mesg) throws RemoteException; }
Now that you’ve
seen both interfaces, let’s
look at the
TickerServer
implementation (Example 22-5). Its constructor starts a background thread
to “track” stock prices; in fact, this implementation
just calls a random number generator. A real implementation might use
a third RMI service to track actual stock data. The connect( )
method is trivial; it just adds the given client (which
is really an RMI proxy for the client server running on your
desktop). The run method runs forever; and on each iteration, after
sleeping for a while, it picks a random stock movement and reports it
to any and all clients that are registered. If there’s an error
on a given client, the client is removed from the list.
Example 22-5. TickerServerImpl.java
package com.darwinsys.callback; import com.darwinsys.client.*; import java.rmi.*; import java.rmi.server.*; import java.util.*; /** This is the main class of the server */ public class TickerServerImpl extends UnicastRemoteObject implements TickerServer, Runnable { ArrayList list = new ArrayList( ); /** Construct the object that implements the remote server. * Called from main, after it has the SecurityManager in place. */ public TickerServerImpl( ) throws RemoteException { super( ); // sets up networking } /** Start background thread to track stocks :-) and alert users. */ public void start( ) { new Thread(this).start( ); } /** The remote method that "does all the work". This won't get * called until the client starts up. */ public void connect(Client da) throws RemoteException { System.out.println("Adding client " + da); list.add(da); } boolean done = false; Random rand = new Random( ); public void run( ) { while (!done) { try { Thread.sleep(10 * 1000); System.out.println("Tick"); } catch (InterruptedException unexpected) { System.out.println("WAHHH!"); done = true; } Iterator it = list.iterator( ); while (it.hasNext( )){ String mesg = ("Your stock price went " + (rand.nextFloat( ) > 0.5 ? "up" : "down") + "!"); // Send the alert to the given user. // If this fails, remove them from the list try { ((Client)it.next( )).alert(mesg); } catch (RemoteException re) { System.out.println( "Exception alerting client, removing it."); System.out.println(re); it.remove( ); } } } } }
As written, this code is not thread safe; things might go bad if one client connects while we are running through the list of clients. I’ll show how to fix this in Section 24.6.
This program’s “server main” is trivial, so I
don’t include it here; it just creates an instance
of the
class we just saw, and registers it.
More interesting is the client application shown in Example 22-6, which is both the RMI client to the
connect( )
method and the RMI server to the
alert( )
method in the server in Example 22-5.
Example 22-6. Callback ClientProgram.java
package com.darwinsys.client; import com.darwinsys.callback.*; import java.io.*; import java.rmi.*; import java.rmi.server.*; /** This class tries to be all things to all people: * - main program for client to run. * - "server" program for remote to use Client of */ public class ClientProgram extends UnicastRemoteObject implements Client { protected final static String host = "localhost"; /** No-argument constructor required as we are a Remote Object */ public ClientProgram( ) throws RemoteException { } /** This is the main program, just to get things started. */ public static void main(String[] argv) throws IOException, NotBoundException { new ClientProgram().do_the_work( ); } /** This is the server program part */ private void do_the_work( ) throws IOException, NotBoundException { System.out.println("Client starting"); // First, register us with the RMI registry // Naming.rebind("Client", this); // Now, find the server, and register with it System.out.println("Finding server"); TickerServer server = (TickerServer)Naming.lookup("rmi://" + host + "/" + TickerServer.LOOKUP_NAME); // This should cause the server to call us back. System.out.println("Connecting to server"); server.connect(this); System.out.println("Client program ready."); } /** This is the client callback */ public void alert(String message) throws RemoteException { System.out.println(message); } }
In this version, the client server alert( )
method
simply prints the message in its console window. A more realistic
version would receive an object containing the stock symbol, a
timestamp, and the
current price and relative price
change; it could then consult a GUI control to decide whether the
given price movement is considered noticeable,
and pop up a
JOptionPane
(see Section 13.8) if
so.
3.138.120.136