Request-Reply JMS with Spring 2.0

Mark Fisher

Several months ago, I posted a blog entry introducing Spring 2.0's support for Message Driven POJOs. While many people are now familiar with that feature, Spring 2.0's JMS remoting features have received less attention. Essentially, this remoting functionality provides a JMS-based version of Spring's general approach to remoting as exhibited in its support for RMI, Hessian/Burlap, and its own HttpInvoker.

For those unfamiliar with Spring remoting, the general idea is to configure a non-invasive exporter on the server-side and a proxy generator (a Spring FactoryBean) on the client-side.

I will demonstrate this JMS remoting here with a code example – based on the same example as in my previous post.

First, there is the service itself. This time I am providing an interface (useful for the proxy generation and a good practice in general):

public interface RegistrationService {
   RegistrationReply processRequest(RegistrationRequest request);
}

…and the simple implementation class is the same as in the previous post:

public class RegistrationServiceImpl implements RegistrationService {
   
    private Map registrations = new HashMap();
    private int counter = 100;
   
    public RegistrationReply processRequest(RegistrationRequest request) {
        int id = counter++;
        if (id % 5 == 0) {
            id = -1;
        }
        else {
            registrations.put(new Integer(id), request);
        }
        return new RegistrationReply(request.getName(), id);
    }
}

The RegistrationRequest and RegistrationReply objects are also the same as in the previous post:

public class RegistrationRequest implements Serializable {
       
    private String name;
       
    public RegistrationRequest(String name) {
        this.name = name;
    }
       
    public String getName() {
        return name;
    }
}
public class RegistrationReply implements Serializable {

    private String name;
    private int confirmationId;
       
    public RegistrationReply(String name, int confirmationId) {
        this.name = name;
        this.confirmationId = confirmationId;
    }
       
    public String toString() {
        return (confirmationId >= 0)
                ? name + ": Confirmed #" + confirmationId
                : name + ": Not Confirmed";
    }
}

This time the 'shared-context.xml' file will be a bit simpler, because I am not defining a reply queue. Instead, the Spring-generated proxy will be leveraging a standard JMS QueueRequestor that relies upon temporary queues for handing the replies:

<?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.0.xsd"
>

       
    <bean id="requestQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="requestQueue"/>
    </bean>
 
    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616"/>
    </bean>
 
</beans>

(as you may notice, I am also using ActiveMQ version 4.1.0 this time)

Now for the most interesting changes. On the server-side, I need an exporter. Specifically, I need to configure an instance of Spring's JmsInvokerServiceExporter. This will actually replace the message listener from the previous post's example. Notice that it requires two properties – the fully-qualified name of the 'serviceInterface' and the 'service' itself (a bean reference to the POJO service to be exported):

<?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.0.xsd"
>

    <import resource="shared-context.xml"/>
       
    <bean id="registrationService" class="blog.jms.remoting.RegistrationServiceImpl"/>
   
    <bean id="listener" class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
        <property name="service" ref="registrationService"/>
        <property name="serviceInterface" value="blog.jms.remoting.RegistrationService"/>
    </bean>
       
    <bean id="container" class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="messageListener" ref="listener"/>
        <property name="destination" ref="requestQueue"/>
    </bean>
       
</beans>

On the client side, the corresponding JmsInvokerProxyFactoryBean is quite simple. Also, notice that there is no longer a need for a jmsTemplate for sending messages as in the previous example. It only needs a reference to the 'connectionFactory', the 'queue', and of course the 'serviceInterface' that the proxy needs to implement:

<?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.0.xsd"
>

    <import resource="shared-context.xml"/>
       
    <bean id="registrationService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="queue" ref="requestQueue"/>
        <property name="serviceInterface" value="blog.jms.remoting.RegistrationService"/>
    </bean>

</beans>

The RegistrationServiceRunner is still necessary to bootstrap the service for this standalone example:

public class RegistrationServiceRunner {
       
    public static void main(String[] args) throws IOException {
        new ClassPathXmlApplicationContext("/blog/jms/remoting/server-context.xml");
        System.out.println("RegistrationService started");
        System.in.read();
    }
}

The client (RegistrationConsole in this case) is slightly different in this example than in the previous Message-Driven POJOs post:

public class RegistrationConsole {
       
    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("/blog/jms/remoting/client-context.xml");
        RegistrationService registrationService = (RegistrationService) context.getBean("registrationService");
               
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));     
               
        for (;;) {
            System.out.print("To Register, Enter Name: ");
            String name = reader.readLine();
            RegistrationRequest request = new RegistrationRequest(name);
            RegistrationReply reply = registrationService.processRequest(request);
            System.out.println(reply);
        }
    }
}

Instead of sending an asynchronous message and having its reply handled by another listener, here the client simply invokes the method on the interface. Spring's exporter and proxy fully manage the correlation IDs.

Thanks to the Spring-generated proxy and its role in encapsulating the request/reply mechanism, the client invocation of this JMS-based remote service is completely decoupled from the JMS infrastructure.

As in the previous post, to run the example requires 3 steps: start the ActiveMQ broker, run the RegistrationServiceRunner's main(..) method, and then run the RegistrationConsole's main(..) method.

Similar Posts

Share this Post
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Slashdot
  • Technorati
  • TwitThis
 

23 responses


  1. Lingo is a related library that might be of interest.


  2. Is it required to only send/receive a single object via the remote method? In others, does the spring impl support arbitrary method signatures as Lingo does? Does it support exceptions? Both async and sync?


  3. Barry,

    Arbitrary method signatures are supported (it passes the context as an invocation object). Also, any Exception (runtime or checked) can be thrown from the service method just as if it were a local call. Of course, JMSExceptions may occur as well – and will be wrapped in Spring's runtime RemoteAccessException.


  4. Very nice. Thanks!


  5. Is there a way to plug different serializability methods (ex: XML binding) instead of relying on standard java serialization?


  6. Hi, I want to implement 'event isolation' or message ordering in base of some key property in the message, can in someway Spring remoting help achieving this?


  7. Great article! I second Davide's motion for pluggable serialization. I'm thinking both the service exporter and the invoker proxy could both be modified to use a MessageConverter. A default message converter could apply the same java serialization behavior that you have right now.


  8. Hi Mark,

    Could you please post your whole example here or send it to me at dxxvi@hatrang.net? I don't know how to write the Spring application context xml file (actually, I wrote one but it was syntactically wrong).

    Regards.


  9. I used the example above to implement the jms request/reply with tibco as the jms provider. It worked fine when I used spring for the client like the example above but when I had a Tibco requestor, the server (listener) never received the message. Does the client have to use the JmsInvokerProxyFactoryBean for this to work? Thanks for any insight!


  10. [quote comment="28562"]Does the client have to use the JmsInvokerProxyFactoryBean for this to work?[/quote]

    Richard,

    The JmsInvokerServiceExporter is expecting a JMS ObjectMessage where the payload is an instance of Spring's RemoteInvocation (basically a serializable wrapper for an AOP MethodInvocation), and the JmsInvokerProxyFactoryBean expects a RemoteInvocationResult. If you would like to create an alternative implementation, I would recommend looking at the source for those two. You may find that you can accomplish what you want through subclassing and/or leveraging a custom implementation of the MessageConverter interface to handle translation to/from the remote invocation wrappers.

    Hope that helps,
    Mark


  11. Thanks Mark, I will take a look at the source.


  12. [quote comment="17494"]Is there a way to plug different serializability methods (ex: XML binding) instead of relying on standard java serialization?[/quote]
    [quote comment="21505"]…both the service exporter and the invoker proxy could both be modified to use a MessageConverter.[/quote]

    A custom MessageConverter implementation can be provided for both exporter and invoker as of version 2.0.5.


  13. [quote comment="17742"]Hi, I want to implement 'event isolation' or message ordering in base of some key property in the message, can in someway Spring remoting help achieving this?[/quote]
    Indrit,

    Could you elaborate on this requirement a bit? My initial reaction is that it would require some customization – if you are talking about a splitter/aggregator model. For that scenario, QueueRequestor is too fine-grained – each request/reply unit-of-work is operating on a single message.


  14. Great post,
    But what about performance?
    When testing the performance of this request/reply solution, it turns out that only about 14 requests per second gets processed at full speed on a "yesteryear"-machine.
    (Just using a loop that sends the next request when the reply arrives)

    Is this somehow due to lack of pooling?
    Or is it maybe that the proxy creates a new temporary reply queue for each request?


  15. Hi I'm testing spring 2.5. So I've been porting the sample using the new JMS namespace. So, while the original sample is working fine, when I change the content of server-context.xml like here:

    I get the following exception

    Caused by: java.lang.NoSuchMethodException: blog.jms.remoting.RegistrationServiceImpl.processRequest(org.springframework.remoting.support.RemoteInvocation)

    I think I've to write a custom message converter but I do not understand why

    Thanks
    Andrea


  16. Sorry, it's me again because I've forgot to quote the xml…

    [quote post="147"]

    [/quote]


  17. Let's try again…

    <jms:listener-container connection-factory="connectionFactory"
    container-type="default" destination-type="queue" >
    <jms:listener destination="requestQueue"
    ref="registrationService" method="processRequest" />
    </jms:listener-container>


  18. Great stuff!

    But I have a question:

    When the connectionFactory brokerUrl is changed to ssl://192.168.0.2:443, where 192.168.0.2 could be any valid address other than localhost, and the environment variable ACTIVEMQ_OPTS is set to

    -Xmx512M -Dorg.apache.activemq.UseDedicatedTaskRunner=true -Dderby.storage.fileSyncTransactionLog=true -Dcom.sun.management.jmxremote -Djavax.net.ssl.keyStore=C:\work\ActiveMQ401\broker.ks -Djavax.net.ssl.keyStorePassword=secret -Djavax.net.ssl.trustStore=c:\work\ActiveMQ401\broker.ts -Djavax.net.ssl.trustStorePassword=secret

    performance slows down to approximately 1/2 min. per message/response compared to less than 1 sec. when set to tcp://192.168.0.2:61616.

    Does it mean this is unusable in production environments?

    Thanks,
    Rolando


  19. I would like to thanks you for ur sincere efforts in writing this post.


  20. Anyone know if there is a way to prefix the names of the queues that are created dynamically.


  21. i love it :D


  22. Cool, nice news!


  23. I can understand the effort behind the posting the code

    this will request for the queue

Leave a Reply