 |
Simple Inter-JVM communication... The Grail!
Posted by cajo on September 03, 2007 at 04:19 PM | Comments (18)
I am very pleased to announce a most significant breakthrough from the the cajo project, in the ease with which distributed computing can be accomplished in Java; and in only 20 kilobytes. It works with all JREs, 1.3 and later. (And before you Rocket Scientists out there ask; yes, it's also 64-bit clean ;)
Just three methods: (click the link, for greater detail)
void export(Object object);
This makes any object's public methods remotely callable
Object[] lookup(Class methodSet);
This finds remote objects matching the given interface
Object proxy(Object reference, Class methodSet);
This creates a local proxy for using the remote object
That's it. These allow for complete interoperability between distributed JVMs. It just can't get any easier than this.
Any object can be exported for remote object invocation, irrespective of the classes it implements, or interfaces it extends. An exported object can be used by a client, simply if it is compatible with the signature requirements specified by the client. Argument and return type coërcion is applied, in compliance with the language specification.
Comfortably, the exported methods can be used with the same syntax and language conventions as local methods. However, it is always well to keep in mind the Eight Fallacies of Distributed Computing. [pdf warning]
Strictly speaking the simple 3 method Grail interface could be implemented by other distributed frameworks; Jini, EJB, Spring, etc. So I've placed the interface specification into the public domain: To leave that as a bit of an open challenge! ;-)
OK, time for an example: Server.java
import gnu.cajo.Cajo; // The cajo implementation of the Grail
public class Server {
public static class Test { // remotely callable classes must be public
public String foo(Object bar, int count) {
System.out.println("foo called w/ " + bar + ' ' + count + " count");
return "Thanks";
}
public Boolean bar(int count) {
System.out.println("bar called w/ " + count + " count");
return Boolean.TRUE;
}
public boolean baz() {
System.out.println("baz called");
return false;
}
} // but needn't be defined in the same class
public static void main(String args[]) throws Exception { // unit test
Cajo cajo = new Cajo(1198, null, null);
cajo.export(new Test());
System.out.println("Server running");
}
}
Compile via: javac -cp grail.jar;. Server.java
Execute via: java -cp grail.jar;. Server
Now for a client: Client.java
import gnu.cajo.Cajo;
interface ClientSet { // client interfaces need not be public
void baz();
boolean bar(Integer quantum);
Object foo(String barbaz, int foobar);
} // the order of the client method set does not matter
public class Client {
public static void main(String args[]) throws Exception { // unit test
Cajo cajo = new Cajo(0, null, null); // port does not matter for clients
Thread.currentThread().sleep(100); // allow a little discovery time
Object refs[] = cajo.lookup(ClientSet.class);
if (refs.length > 0) {
System.out.println("Found " + refs.length);
ClientSet cs = (ClientSet)cajo.proxy(refs[0], ClientSet.class);
cs.baz();
System.out.println(cs.bar(new Integer(77)));
System.out.println(cs.foo(null, 99));
} else System.out.println("No servers found");
System.exit(0);
}
}
Compile via: javac -cp grail.jar;. Client.java
Execute via: java -cp grail.jar;. Client
That's all there is to it. Detailed information, including source code, can be found here, also the main project site is a very helpful resource.
Technically it's still beta, and will be officially added to the cajo project soon, however there are no known issues at this time. So please feel free to kick the tyres, and take her out for a spin!
Enjoy, and as always, your comments and feedback are encouraged.
-John
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
I like it! :)
Posted by: ivan_memruk on September 03, 2007 at 11:57 PM
-
I noticed in your example client that you are looking up ClientSet interfaces. However, your server doesn't implement the ClientSet interface specifically. It does implement methods of the same signature as ClientSet, but doesn't explicitly "implement" ClientSet. Is that just a typo?
Posted by: joconner on September 04, 2007 at 11:47 AM
-
Hi John, No, this is completely intentional. Often a server will implement a much richer collection of methods than a client needs. The ClientSet means quite literally that; a set of methods solely of interest to the client. I blogged on the substution topic a little while back, here.John
Posted by: cajo on September 04, 2007 at 11:57 AM
-
Very interesting.
We use RMI callbacks to achieve some "push" from the Server to the Client. How could you implement that?
Many thanks,
-Damian
Posted by: jamianb on September 04, 2007 at 12:06 PM
-
Hi Damian,
There is nothing to stop a client from exporting objects of its own as well. Whilst the distinction of a server being one who exports objects, and a client being one who looks up objects; all JVMs are completely free to do both.Now, if the server is trying to asynchronously callback clients behind a firewall; well, we even have a solution for that.John
Posted by: cajo on September 04, 2007 at 12:14 PM
-
You have to "generify" the interfaces, haven't you?
Posted by: ewin on September 04, 2007 at 02:53 PM
-
This can be a loaded term: "generify" ...
Let me try to be a little more specific. What is being done here is implicitly agreeing that a matching set of methods, or at least matching closely enough, constitutes a functional contract between a client and server. This is not that big of a leap from using the interface type; as a client of an interface typically has no knowledge of the service implementation.
This approach can in fact offer more flexibility than simply contracting by type; as a service could potentially furnish functionality to a client that merely needed a similar subset, of what it can provide.
Most importantly: The more expressive the method names, and the types of arguments they use; the less likely there would be an opportunity for mis-communication.
Posted by: cajo on September 04, 2007 at 03:28 PM
-
So the exported/looked-up method set can declare custom argument and return types?
Posted by: steevcoco on September 04, 2007 at 04:35 PM
-
Yes indeed. However one of two situations come into play:
If the JVMs have these custom classes in their respective codebases, then all will work, without issues.
If not, then the classes can be dynamically loaded, from one JVM to another. Please see this page for a little more insight.
Posted by: cajo on September 04, 2007 at 05:41 PM
-
To get rid of the cast (on Java 1.5+), couldn't you defing the proxy method as public T proxy( Object[] refs, Class clazz ) Of course, assuming you want to get rid of the cast, and make Java 5 a minimum requirement ;)
Posted by: timyates on September 05, 2007 at 01:05 AM
-
Gah... stupid formatting... that should be: public <T> T proxy( Object[] refs, Class<T> clazz )
Posted by: timyates on September 05, 2007 at 01:07 AM
-
Hi John, looks cool! I will have to check cajo out more . One big question -
what about synchronization issues? I assume that the object being
served will have to provide its own methods to acquire and release locks
as required by object design?
Posted by: tpoindex on September 05, 2007 at 10:12 AM
-
You are exactly right! Think with regard to synchronisation no differently than you would for local objects.
Posted by: cajo on September 05, 2007 at 10:18 AM
-
Hmm...I suppose I'm at first a bit distracted by the fact that I can discover classes that implement just any set of method signatures instead of an actual interface. Unfortunately I don't really know if that presents a problem. How does finding a remote set of Objects help me if I don't know specifically what type they are? Sure, I suppose method signatures help, but knowing the actual object type seems important.
Posted by: joconner on September 05, 2007 at 11:35 AM
-
Possibly you are placing too much credence in "type."
What is an interface really? It is set of methods, needed by a client, and offered by a server. The type simply "names" this set.
What if the client did not know this "name?"
Consider: If one just wanted to get from one part of the city to another, all other considerations being equal; the metro, a taxi, or even a helicopter, could suffice.
Why, depending on the city; a jinriksha too, even if you didn't know what one was! ;-)
I think it puts us in a context a little bit more like real life.
Posted by: cajo on September 05, 2007 at 01:16 PM
-
I have to admit...something about this still really bothers me. An interface is not just a set of methods, although it does define methods. I think an interface also tells us a little about the implementing class as well.
For example, consider the two interfaces LightSwitch and Bomb:
interface LightSwitch {
void activate();
}
interface Bomb {
void activate();
}
Yes, both interfaces define the same method name, but their context is very different. In your framework, I can ask for any object that has an activate method...and instead of getting a LightSwitch, perhaps I get a Bomb instead. In this case, the end result seems very different, despite the method name being the same.
I suppose what I'm saying is that just because two classes implement the same method name doesn't mean the methods do anything similar. Its the interface name that is supposed to help me understand that two objects behave similarly -- although in theory I suppose they could do entirely unrelated things.
Posted by: joconner on September 07, 2007 at 05:27 PM
-
I understand your concern John, nothing is fool-proof.
For example, there is nothing to stop someone doing:
interface Bomb extends LightSwitch {}
So you can't always rely on the type to help you either.
Yet, the following could make perfect (albeit warped) sense:
interface Water {
Water pump();
...
}
interface Pump {
Water pump();
...
}
Fortunately for all of us developers; good design is still required. ;-)
Posted by: cajo on September 07, 2007 at 06:24 PM
-
All things being equal, I see the ClientSet in John's example as
a sort of filter. Taking that view, it should be reasonable to
architect a use case where various Items implement some
sort of "marker" method like BobV1_5.
Then, those clients which want BobV1_5 will get the right activate()
method rather than the activate() from a FooV666.
Seems to work O.K. with testing on my own LAN running
multiple machines and JVMs.
Posted by: nhebert on September 17, 2007 at 03:12 AM
|