Three cases. Either its us, o r its our stub, or its not equal.
our stub can arise, for example, if one of our methods took an instance of
Account. A client could then pass in, as an argument, our stub.
if object instanceof Account_Imp l2 { return object == this;
} if object instanceof RemoteStub {
try { RemoteStub ourStub =
RemoteStubRemoteObject.toStubthis; return ourStub.equalsobject;
} catchNoSuchObjectException e{
were not listening on a port, therefore its not our
stub }
} return false;
} public int hashCode {
try { Remote ourStub = RemoteObject.toStubthis;
return ourStub.hashCode ; }
catchNoSuchObjectException e{} return super.hashCode ;
} }
8.2.3 Extending UnicastRemoteObject
We now have two candidate server objects that are almost identical. The only difference is that Account_Impl
extends UnicastRemoteObject
, and Account_Impl2
doesnt. In order to choose between these options, we need to examine exactly what extending
UnicastRemoteObject does.
There are two main benefits to UnicastRemoteObject
: it automatically connects to the RMI runtime and it knows how to check for equality with other remote objects. However, extending
UnicastRemoteObject can sometimes cause minor problems for two reasons: it prevents
server classes from subclassing other classes because Java is a single inheritance language, and it can sometimes prematurely expose an object to remote method calls.
8.2.3.1 The benefits of UnicastRemoteObject
UnicastRemoteObject has three constructors. They are:
protected UnicastRemoteObject protected UnicastRemoteObjectint port
protected UnicastRemoteObjectint port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf
RMI is built as a layer on top of sockets. By default, RMI uses the standard sockets defined in the
java.net package. But
you can choose to use different types of sockets by creating a socket factory. Well talk about socket factories in more detail
in Chapt er 18
. For now, the third constructor can be ignored.
The first method is documented as, Create and export a new UnicastRemoteObject
object using an anonymous port. This means that the RMI runtime will choose which port to use. In the
latter two constructors, you must specify a port. In either case, the port is the port number on which a
ServerSocket associated with the server listens.
If you specify a port, youre ensuring that the server will listen for connections on that specific port. This is because the constructor for
UnicastRemoteObject automatically hooks the
instance of UnicastRemoteObject
into the RMI runtime™as soon as the constructor for UnicastRemoteObject
returns, the object is actually listening for remote method calls.
If you dont specify a port, then RMI can reuse the same server socket for multiple servers. Well discuss the ins and
outs of socket reuse more thoroughly in Chapt er 16
. For now, its enough to know that, unless you need to specify the
port, letting RMI do so can help conserve system resources.
While nothing in the code for Account_Impl
actually did anything related to RMI, the implicit call to
UnicastRemoteObject s constructor did. This means that the launch code associated with
the bank example needs to do only two things: create the servers, and register them with the naming service. The launch code for
Account_Impl is, essentially, a loop around the following
two lines of code: Account_Impl newAccount = new Account_ImplserverDescription.balance;
Naming.rebindserverDescription.name, newAccount; If, on the other hand, we dont subclass
UnicastRemoteObject , well need to explicitly register
our listeners with the RMI runtime as well as with the naming services. The launch code for Account_Impl2
is, essentially, a loop around the following three lines of code: Account_Impl2 newAccount = new Account_Impl2serverDescription.balance;
RemoteStub stub = UnicastRemoteObject.export ObjectnewAccount; Naming.rebindserverDescription.name, stub;
exportObject is a static method defined on
UnicastRemoteObject that starts the RMI
runtime listening for messages to an instance of a server class. There are actually three such methods, which are parallel to
UnicastRemoteObject s constructors:
static RemoteStub exportObjectRemote obj static Remote exportObjectRemote obj, int port
static Remote exportObjectRemote obj, int port, RMIClientSocketFactory csf,
RMIServerSocketFactory ssf
In spite of the declared return types of the two final methods, these methods all return instances of
RemoteStub .
The other benefit of extending UnicastRemoteObject
is that UnicastRemoteObject
implements equals
correctly. If you look at the documentation for UnicastRemoteObject
, it contains the following, rather cryptic, assertion:
Objects that require remote behavior should extend RemoteObject
, typically via UnicastRemoteObject
. If UnicastRemoteObject
is not extended, the implementation class must then assume the responsibility for the correct
semantics of the hashCode
, equals
, and toString
methods inherited from the Object
class, so that they behave appropriately for remote objects. This comment is mostly a reference to the problems associated with passing around stubs for
remote servers. What happens when you need to tell whether a stub is equal to a server? For example, in
Chapt er 7 we speculated about the possible need for a
transferMoney method call with the following signature:
public void transferMoneyAccount destinationAccount, Money amount throws
RemoteException, OverdraftException, NegativeAmountException; Its quite conceivable that problems will arise if
destinationAccount is the server that
receives the transferMoney
call. This means we should do two things: •
Create a new exception type, DuplicateAccountException
, and declare the method as throwing it as well:
public void transferMoneyAccount d estinationAccount, Money amount throws
RemoteException, OverdraftException, NegativeAmountException, DuplicateAccountException
• Add checking code to our implementation of
transferMoney along the lines of, If
the destination account is the same as the source account, throw a DuplicateAccountException
. This second step should be simple and should boil down to the following code:
if equalsdestinationAccount { throw exception
} If our implementation of
Account extends
UnicastRemoteObject , this will work because
UnicastRemoteObject s
equals method handles stubs correctly. If our implementation of
Account does not extend
UnicastRemoteObject , then well need to override
equals to
handle the case of stubs ourselves. UnicastRemoteObject
handles hashCode
in a similar manner: the hash of a server is equal to the hash of its stub.
Overriding equals
and hashCode
doesnt require a lot of code, but it is rather tricky. You need to worry about three distinct things: the computer on which the server runs, the identity
of the JVM in which the server runs,
[ 1]
and the identity of the server object inside the JVM. The best course of action is to rely on stubs to get it right. Thats why in the
Account_Impl2 code,
the implementations of equals
and hashCode
worked by obtaining a stub and relied on the fact that the people at Sun Microsystems, Inc., who implemented RMI, know how to find out
these three pieces of information.
[ 1]
Since more than one JVM can be running on a single computer, you need to worry about which JVM contains a specific object.
Since the RMI runtime maintains hashtables of servers and stubs, you actually do need to override
equals and
hashCode if there is a chance that a server could be
compared to a stub.
8.2.3.2 The costs of UnicastRemoteObject