A few days with Spring.

I have been learning Spring 2.5 for the last few days. My usual method of learning a new tool is to
  1. buy a book
  2. skim the book in chapter order
  3. study specific chapters as I need to know their contents
  4. disregard the chapter's examples because they suck either because they are too simple or inelegant
  5. unsuccessfully google for better examples
  6. successfully google for clues
  7. piece together a simple and elegant example
Now, I understand that beauty is in the eye of the beholder and so my examples probably suck to others. However, my track record is that they suck less.

Spring is funky. I had wanted to use EJB3 for this project but Spring won out. The reasons have little to do with technical merit. From a larger software engineering perspective EJB3 is just too unknown among the in-flight-magazine readers. Ok, that was a dig and childish. Sorry.

For Spring to work on this project I need for it to handle REST, JMS, and RMI simply and elegantly. To this end I have been working on JMS and RMI stuff mostly.

Spring's "JMS template" stuff is very old-school Java. A better approach is to use generics for clean coding and have the configuration done in XML. After some effort I can now code a JMS listener as simply as
public class HelloMessageListener extends MessageListener<String> {

@Override
public void receive( String message ) {
// handle string message
}
}

And, if your message is more complex the listener's complexity does not change. The generic MessageListener is where the hard stuff is and, to be honest, it is not very hard
public abstract class MessageListener<T> implements javax.jms.MessageListener {

private MessageConverter<T> messageConverter;

@Required
public void setMessageConverter( MessageConverter<T> messageConverter ) {
this.messageConverter = messageConverter;
}

@Override
public void onMessage( Message jmsMessage ) {
T message = messageConverter.fromJMS(jmsMessage);
receive( message );
}

abstract public void receive( T message );
}

MessageConverter also needs to be defined for all the machinery to work. The XML configuration is messy but mostly due to Spring's need for factories everywhere. In the end, the JMS listener is declared simply as
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
...
<property name="messageListener" ref="helloMessageListener" />
</bean>

...

<bean name="helloMessageConverter" class="example.HelloMessageConverter" />

<bean name="helloMessageListener" class="example.HelloMessageListener">
<property name="messageConverter" ref="helloMessageConverter" />
</bean>

For RMI Spring has good POJO support out of the box. What I could not find, however, was support for exporting a POJO as an RMI service that could be called by a non-Spring client. For example, the following client can be run using the standard JDK
import java.rmi.Naming;

public class HelloServiceCaller {

static public void main( String[] args ) throws Exception {
String helloServiceUrl = "rmi://localhost:1199/HelloService";
HelloRemoteService helloService = (HelloRemoteService) Naming.lookup(helloServiceUrl);
for ( String input : args ) {
String output = helloService.sayHello( input );
System.out.println( input + " -> " + output );
}
}
}

But if you try to use this against an RMI service exported via Spring's org.springframework.remoting.rmi.RmiServiceExporter it does not work. You get a java.lang.ClassCastException regards org.springframework.remoting.rmi.RmiInvocationWrapper_Stub. Yuck.

However, with a little effort I was able to create an exporter that could export a POJO as a remote service as long as the POJO had methods that correspond to a valid remote interface. For example,
public interface HelloRemoteService extends Remote {
public String sayHello( String name ) throws RemoteException;
}
...
public class HelloService {
public String sayHello( String name ) { return "Hello " + name; }
}

To remote HelloService I wrote RemoteProxyExporter which is a mixture of Spring, RMI, Proxying, and Reflection.

Now RemoteProxyExporter (and friends) was tricky to write but it took less than a day's effort. As did MessageListener (and its friends). Spring might have a means of achieving the same ends as my code but so far I have not found it. There are two reasons for this. The first is that Spring is highly abstract and data-driven and so this richness just requires lots more exposure than I have yet had. The second is that Spring is old, really old.

During Spring's life Java has had many changes of course as to how common problems are solved. For example, how to augment and/or configure a Java object. First we used reflection. When this didn't solve all our needs reflection was augmented with "information" classes (such as BeanInfo). When this was seen as too static for a dynamic world the information classes where replaced/augmented with external declarations encoded in XML. When XML was seem as cumbersome Java 5 added annotations. Spring uses all of this but not consistently. And so often times just understanding how to connect A with B is a nightmare. And googling does not help because the definitive answer for some questions was given in 2009 while others where given in 2004 when Spring 1.0 was released.

The upshot of all this effort is that I will be able to do what I need to do and do it in a way that works for all staff skill levels. And do it simply and elegantly.

I can't wait to dig into pooling!

No comments: