Monday, December 10, 2007

Zero to RMI in Minutes (or I *heart* Spring)

Recently, I paired with a colleague and sought to experiment with detached objects in Hibernate across a remote connection. Armed with some POJOs and Hibernate files from a project source tree, we decided to write a toy client and server from scratch.

(Admittedly, we had experience with Spring IOC: this is not a tutorial.)

We were fully experimenting with the sandbox in 75 minutes. This may not seem fast but it was astonishing: some days, it seems I can't empty my Recycle Bin in 75 minutes.

Partly for my own reference, but also for CtJ readers, here is a recap of the RMI portion. No Hibernate here. And an extremely "toy" project structure (e.g. no Ant, no packages). But the key idea is there, and it will showcase the power of Spring.

We used Spring 2.5. To be honest, I don't know if this is in Version 1.x. It may well be.

Here is the contract between the client and server. This would be in a common package:


interface EasyServer {
Message exchangeMessage(String user);
}

Here is a simple Message class. Of course, for RMI, being Serializable is vital:


import java.io.Serializable;

public class Message implements Serializable {
private final String message;

public Message(String message) { this.message = message; }

public String getMessage() { return message; }
}

So far, so good. Now the server implementation:


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EasyServerImpl implements EasyServer {

public Message exchangeMessage(String user) {
return new Message(user + ", Spring rocks!");
}

public static void main(String[] args) {

ApplicationContext context = new
ClassPathXmlApplicationContext("server_config.xml");

context.getBean("easyServer");
System.out.println("server ready");
}
}

It defines an easy exchangeMessage method and a starting main method. Spring takes care of the rest, as shown in the server_config.xml file:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!-- EasyServerImpl is the implementation -->

<bean id="easyServer" class="EasyServerImpl">
</bean>

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- register the impl as the 'easyServerService' -->
<property name="serviceName" value="easyServerService"/>
<property name="service" ref="easyServer"/>
<property name="serviceInterface" value="EasyServer"/>
<!-- defaults to 1099 -->
<property name="registryPort" value="1199"/>
</bean>

</beans>

For running and compiling the server, simply ensure that these are in the CLASSPATH:
  • SPRING_HOME/dist/spring.jar
  • SPRING_HOME/lib/jakarta-commons/commons-logging.jar

Then it is up and away:


$ java EasyServerImpl

[ Spring magic dust snipped ]

server ready

The client is even simpler:


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EasyClient {
private EasyServer easyServer;

public void setEasyServer(EasyServer easyServer) {
this.easyServer = easyServer;
}

public Message exchangeMessage(String user) {
return easyServer.exchangeMessage(user);
}

public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("client_config.xml");

EasyClient easyClient =
(EasyClient) context.getBean("easyClient");

String output =
easyClient.exchangeMessage("CtJ Reader").getMessage();

System.out.println( output );
}
}

The client-side configuration XML:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="easyClient" class="EasyClient">
<property name="easyServer" ref="easyServer"/>
</bean>

<bean id="easyServer"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl"
value="rmi://localhost:1199/easyServerService"/>
<property name="serviceInterface" value="EasyServer"/>
</bean>

</beans>

Using the same CLASSPATH, but with a separate window, we establish contact like so:


$ java EasyClient

[ Spring noise snipped ]

CtJ Reader, Spring rocks!

This trivial example uses approximately 50 lines of Java, with no knowledge of RMI. Amazing. It an easy exercise for the reader to package it up into a more formal project, using Ant etc. It is more challenging to add Hibernate, but still tractable: the basic wiring for a client-server playground is right here.

I'm curious as to how many lines of code this would take in Groovy. I think 40 would be easy to attain.

That's cool. I *heart* Spring!

ps. I lifted "*heart*" from Eric.

3 comments:

Jay said...

Just a side-note: exception handling is extremely simple. If your interface throws SomeException, you don't need to think about wrapping exceptions via java.rmi.RemoteException. Instead, you simply define and throw your exceptions, as with the rest of your classes, Spring simply gets your throwable across.

Michael Easter said...

Great point, Jay... thanks for the note

Tarun said...

Nice and easy tutorial :)

I googled a lot but failed to find a good tutorial or Spring-RMI on Eclipse IDE.
I am trying to implement your tutorial in eclipse, I am able to run the server side code, but client side code is not able to get the proxy.

Can you please help? Both client and server are in different projects but in same workspace.