The benefits of UnicastRemoteObject

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