I'm trying to write my first RMI application, modelling it after the DVD database in Habibi, Patterson, & Camerlengo's SCJD Exam book.
That example has a package sampleproject.db, which knows nothing about RMI, Sockets, or anything like that; in particular, the package includes an interface DBClient which provides the basic database operations but doesn't mention RemoteException.
In the sampleproject.remote package is an interface named DVDDatabaseRemote which implements Remote and DBClient and has nothing in between the curly-braces. A class named DVDDatabaseImpl extends UnicastRemoteObject and implements DVDDatabaseRemote; its various methods throw RemoteException, as required. So far this makes sense to me, and it works: I can javac everything, rmic DVDDatabaseImpl, start the server, run the client, and they talk to one another (at least on the same machine -- I haven't tried it across machines yet).
So I tried something similar. The package edu.adelphi.sbloch.bank, which knows nothing about RMI, contains an interface named AccountDBInterface providing basic bank-account operations. The package edu.adelphi.sbloch.remote contains an interface named RemoteDBInterface which implements Remote and AccountDBInterface and has nothing between the curly braces. A class named RemoteDBImpl extends UnicastRemoteObject and implements RemoteDBInterface. Its constructor throws RemoteException -- no problem -- but when I add "throws RemoteException" to any of the other method headers, I get a compile error that this clause is not compatible with the throws clause in AccountDBInterface. If I leave out the "throws RemoteException", javac likes it but rmic rejects it.
Does the throws clause in a method implementation have to exactly match that in the interface? If so, why does the sample project compile? If not, why doesn't my code compile?
Yes, throwing exceptions are considered part of the method signature, and exceptions can't be added if the interface is to be implemented correctly. The example project wouldn't build like you describe it... could there be some confusion with similiarly named interfaces/methods? Some of which wrap the RemoteExceptions?
Write once, run anywhere, because there's nowhere to hide! - /. A.C.
Joined: Aug 19, 2003
OK, I figured it out. The DBClient interface never mentions RemoteException, but all of its methods throw IOException, which is RemoteException's superclass. So that's why the sample project compiles. My project doesn't, because the methods as I originally wrote them didn't throw IOException. (I was more interested in the RMI aspect than the database aspect, so I wrote a quick-and-dirty in-memory "database" that didn't do any I/O, just to see whether I could get RMI working.)
So I can fix it by changing my interface to throw IOException (or RemoteException or something), even though the original, non-networked implementation of the interface had no need for IOExceptions.
The question remains: what would I do if I wanted to RMI-ize an interface written by somebody else, to whom it never occurred that somebody might write a remote or otherwise I/O-dependent implementation of it? Is there any way to do this without copying and modifying the interface (which makes every OO cell of my body rebel)? [ November 24, 2004: Message edited by: Stephen Bloch ]
The way I've usually done it is make two adapters - one to wrap and convert the non-remote interface to a remote interface, and one to wrap and convert the remote interface back to a non-remote interface... I call this a "double-wrapper adapter", but I don't know if the pattern exists under another name elsewhere.
Usually I prefer to re-throw the RemoteException as an exception created for and used by the application, or I make some kind of generic error-handler that pops up a dialog or something, and send the RemoteException to it to pop up some kind of "Network Down" error message.
Also, things get interesting when non-serializable return types and parameters come into play - you'll have to make adapters for any of those also, and have the client adapter and server adapter cast them back and forth.
I admit that it is kind of crappy OO to copy the methods and have a remote and non-remote version - but this is the best that I've come up with so far... also note that this isn't the preferred way to do RMI - it's basically a workaround for when you can't change the existing interface for one reason or another.
Joined: Aug 19, 2003
OK, that makes sense, although it is sorta ugly. Thanks!