Spring and JMS

One of the most popular and widely used J2EE APIs, certainly in applications that we have worked on, is JMS. JMS provides a standardized API for accessing messaging services from a Java application. A wide range of JMS-compliant messaging services is available. Every J2EE- compliant application server provides a JMS messaging system and a large number of stand- alone messaging systems support JMS, both open source and commercial. With such a large set of choices available, when choosing a messaging provider, you can easily pick one that is ideal for your solution.

We do not go into great detail on choosing a JMS provider; so many are available and there are so many considerations that the discussion would require a chapter unto itself. What we do point out is that you should not assume that the need for JMS mandates the use of a full J2EE server; plenty of stand-alone JMS providers are available. We have enjoyed considerable success on projects using a simple combination of Spring, Tomcat, and ActiveMQ. Of course, if another requirement of your application leads you to choose an application server—for instance, you may want to use message-driven beans to receive JMS messages—then do not avoid the application server. On the other hand, do not let your need for JMS mean that you have to use an application server.

Spring's support for JMS was introduced in version 1.1 and a whole set of additional functionality is scheduled for 1.2. In this section, we look at how you can send and receive messages easily, to both queues and topics, using Spring JMS support classes. We also look at how you can use Spring's PlatformTransactionManager architecture to manage JMS local transactions transparently. In addition to this, we take a peek at the additional functionality scheduled for version 1.2 of Spring.

As with previous sections, we assume that you have a working knowledge of JMS; we won't be discussing JMS basics in any detail.

Introducing ActiveMQ

For the examples in this section, we are going to be using ActiveMQ, which is available from Codehaus at http://activemq.codehaus.org. ActiveMQ is an open source, stand-alone JMS messaging service written in 100-percent Java. Because this is a book on Spring, we do not spend a vast amount of time on ActiveMQ, and thankfully you do not need to perform any configuration to get ActiveMQ up and running—other than creating a simple startup script, that is. At the time of writing, version 1.1 of ActiveMQ is currently in development, but we chose to use the stable version 1.0 to ensure that you are able to run the examples.

The feature set of ActiveMQ is huge, but importantly, ActiveMQ supports version 1.1 of JMS, which you must have for some of the examples in this section. Of particular interest to Spring developers is the configuration mechanism of ActiveMQ, which is Spring-based and provides the ability to host an ActiveMQ message broker in your Spring application. We do not cover these features here, but you can find out more about them from the ActiveMQ website.

To get started, download the 1.0 release of ActiveMQand extract the files to a directory on your machine. A notable omission from version 1.0, which is fixed in version 1.1, is a startup script. Thankfully it is fairly easy to create one yourself; Listing 13-31 shows the startup script we created for Windows.

Listing 13-31: Startup Script for ActiveMQ

image from book
java -cp "activemq-1.0.jar;lib\commons-logging-1.0.3.jar;¿ lib\concurrent-1.3.4.jar;lib\geronimo-spec-j2ee-1.0-M1.jar" ¿ org.codehaus.activemq.broker.impl.Main tcp://localhost:61626 
image from book

Save this file into the root directory of your ActiveMQ installation with the .bat extension. Unix users need to swap the backslashes (\) for forward slashes (/) and the semicolons (;) for colons (:). Notice in the last line of the startup script that we pass in a single argument to the Main class; this is the URL on which the ActiveMQ message broker should listen for client connections. ActiveMQ supports a number of different communication protocols including TCP, UDP, and SSL. We chose to stick with plain old TCP and instructed the broker to listen on port 61626.

This startup script runs the basic ActiveMQ broker that uses in-memory persistence for messages. This means that if you shut down the ActiveMQ broker, any messages that are not picked up are lost. In the lib directory of the ActiveMQ directory, you will find JAR files for Berkeley DB and JDBM. If you include these JARs (either Berkeley DB or JDBM) in the classpath in your startup script, then ActiveMQ uses a corresponding persistence system for message persistence. See the ActiveMQ website for more details on the different persistence options.

Once you run the startup script, you should receive a message telling you that the ActiveMQ message broker has started and the TCP transport is listening on the configured port.

ActiveMQ, JNDI, JMS, and Spring

One of the nicest features of ActiveMQ is that it is easy to configure. As you saw in the previous section, ActiveMQ runs right out of the box with very little configuration. With a traditional JMS server, such as the one included in a J2EE application server, you configure the JMS ConnectionFactory along with the various Destinations using a vendor-specific mechanism; these are then made available via JNDI. With ActiveMQ, you can configure the ConnectionFactory directly in your Spring ApplicationContext configuration file and inject it into your components just as you would with any standard bean. In addition, you can also configure the different Destinations your application uses in the configuration file; ActiveMQ will create them on the fly as your application attempts to connect. Using these features means that you no longer need to have JNDI service available, nor does your code need to interact with it; in addition, now you can keep the configuration for your messaging resources alongside the resources for the rest of your application.

Throughout this section, we use the ActiveMQ approach to JMS component configuration. If you deploy a Spring-based JMS application into a JMS provider that exposes JMS resources via JNDI, you can make these resources available for DI using the JndiObjectFactoryBean discussed earlier in the chapter.

JMS Domain Model Unification

As you may be aware, JMS defines two distinct messaging domains: point-to-point and publish/subscribe. In version 1.0.2 of JMS, the API was structured in a way that required your application to have prior knowledge of the messaging domain it was using. That is to say, you could not start your application off with a point-to-point model and then switch it to publish/subscribe without making changes to your code. As of version 1.1, the two domains have been unified, and for the most part, your application can remain blissfully unaware.

The JMS helper classes in Spring follow a similar model, allowing your application to code in a domain-independent manner. What makes this interesting is that Spring supports this for both JMS 1.1 and JMS 1.0.2. It should be noted that some of the features we look at, specifically those scheduled for the 1.2 release, are currently 1.1-specific.

The JmsTemplate and JmsTemplate102 Classes

For the most part, applications using Spring JMS interact with the JMS provider via the JmsTem- plate class or the JmsTemplate102 class. The JmsTemplate102 class is actually a subclass of the JmsTemplate, which is intended for use with JMS 1.1 providers, and it simply overrides certain methods to work with JMS 1.0.2 providers. Because we are running against a JMS 1.1 provider, we use the JmsTemplate class instead of JmsTemplate102. Unless otherwise specified, you can simply switch to the JmsTemplate102 class if you are using a 1.0.2 provider.

Sending and Receiving Messages Synchronously

The most basic of operations you can perform using JmsTemplate are send and synchronous receive. In general, JMS is more suited to an asynchronous receive style, but as of version 1.1,

Spring JMS does provide a solution for this. We discuss alternatives in more detail later, in the section entitled "Receiving Messages Asynchronously."

In this example, we are going to create two simple console-based applications: one that sends a single message to a queue, and one that listens for a message and displays it once it is received.

To start with, we need to configure the basic JMS ConnectionFactory and JmsTemplate. Because we use the same ConnectionFactory and JmsTemplate settings for all examples in this section, we factor these settings into a common configuration file. Remember that ActiveMQ allows you to configure the ConnectionFactory in your Spring configuration file. You can create JmsTemplate instances directly in your code, but it is better to configure them in your Spring configuration file. That way, you can change settings without having to edit the code, plus you can easily switch between JmsTemplate102 and JmsTemplate if you decide to swap JMS providers.

Listing 13-32 shows the configuration that is shared by all the examples in this section.

Listing 13-32: Configuring a ConnectionFactory and a JmsTemplate

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>         <!-- configure connection factory -->     <bean                  >         <property name="brokerURL">             <value>tcp://localhost:61626</value>         </property>     </bean>          <bean  >         <property name="connectionFactory">             <ref local="connectionFactory"/>         </property>     </bean>      </beans>
image from book

If you are using a JMS provider that uses JNDI to expose the ConnectionFactory, you can replace the connectionFactory with a JndiObjectFactoryBean to load the ConnectionFactory from JNDI. The only dependency the JmsTemplate class requires is the ConnectionFactory; we set it here using DI. JmsTemplate has a selection of configuration parameters that are mainly related to underlying JMS configuration parameters. You can read more about these in the API docs.

However, there are three properties used by JmsTemplate that you should be aware of. The first property is defaultDestination. It allows you to define a default JMS Destination to send messages to when an explicit Destination is not defined. The second, destinationResolver, allows you to specify the implementation of DestinationResolver that is used to locate Destinations based on a String name. We cover DestinationResolver in more detail later in the chapter.

The final property, pubSubDomain, is only relevant to the JmsTemplate102 class, and it allows you to inform Spring whether or not it should use classes that correspond to the publish/subscribe messaging model in a JMS 1.0.2 environment.

The next step is to configure a Destination to which to send the messages. Because we are just going from one client to another, we can use point-to-point messaging, so we need to configure a JMS Queue. As we mentioned, ActiveMQ allows you to configure Destinations directly in your Spring configuration file, and it creates them on the fly. If you are using a different JMS provider, then you may need to create the Queue explicitly, and you can use JndiObjectFactoryBean to retrieve the Queue from JNDI. Listing 13-33 shows the Queue configuration.

Listing 13-33: Configuring the Hello World Queue

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>             <bean  >         <!-- Set the Queue Name -->         <constructor-arg index="0">             <value>HelloWorldQueue</value>         </constructor-arg>     </bean> </beans>
image from book

Here you can see that we use constructor injection to set the name of the Queue. That is all the configuration needs for this example. To recap, we configured a ConnectionFactory and a JmsTemplate in the common configuration file. The ConnectionFactory is provided to the JmsTemplate as a dependency. In the example-specific configuration file, we configured a single Destination, a Queue. This is where our example application sends to and receives from.

For the code, we start by looking at the HelloWorldSender class which, unsurprisingly, sends the message "Hello World" to the Queue defined in the configuration file. Listing 13-34 shows the code for HelloWorldSender.

Listing 13-34: Sending a Message Using JmsTemplate

image from book
package com.apress.prospring.ch13.jms.helloworld;      import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator;      public class HelloWorldSender {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-helloWorld.xml"                      });              JmsTemplate template = (JmsTemplate) ctx.getBean("jmsTemplate");         Destination destination = (Destination) ctx.getBean("destination");              template.send(destination, new MessageCreator() {             public Message createMessage(Session session) throws JMSException {                 return session.createTextMessage("Hello World");             }         });                  System.out.println("Message Sent");     } }
image from book

Most of this code should look familiar, but notice that when we create the FileSystemXmlApplicationContext, we are passing in a String[] so that both the common and example-specific configuration files get picked up. The interesting part of this application is the call to JmsTemplate.send(). JmsTemplate allows you to send using either send() or convertAndSend(). You see convertAndSend() in action in one of the later examples; for now, we will stick with send(). The send() method has three overloads: one that accepts a Destination and a MessageCreator, as shown in the example; one that accepts a String and a MessageCreator; and one that accepts just MessageCreator. If you use the first of these overloads, Spring sends the Message created by the MessageCreator to the Destination supplied. Using the second overload, Spring attempts to locate the Destination using the supplied destination name and the configured DestinationResolver implementation. We use this overload in one of the later examples. The final overload simply sends the created Message to the configured default Destination, which by default is null. In Listing 13-34, we use an anonymous implementation of MessageCreator that simply creates a TextMessage that contains the text "Hello World".

That is really all there is to sending a message using JmsTemplate. Notice that, in Listing 13-34, we never use any messaging domain-specific classes or interfaces such as Queue. Everything is expressed in generic terms; only the configuration file points to exactly which messaging model is being used, and you can change that without affecting the code.

The final part of this example is the HelloWorldReceiver, which receives a single message from a given Destination, writes it to stdout, and then exits. We configure this class with the same Destination as the HelloWorldSender class, and it is able to pick up messages sent by the HelloWorldSender class. Listing 13-35 shows the code for the HelloWorldReceiver class.

Listing 13-35: Receiving Messages Synchronously with JmsTemplate

image from book
package com.apress.prospring.ch13.jms.helloworld;      import javax.jms.Destination; import javax.jms.Message;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.jms.core.JmsTemplate;      public class HelloWorldReceiver {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-helloWorld.xml"                      });              JmsTemplate template = (JmsTemplate) ctx.getBean("jmsTemplate");         Destination destination = (Destination) ctx.getBean("destination");              System.out.println("Will wait " + template.getReceiveTimeout()                 + " seconds for message");              Message msg = template.receive(destination);         System.out.println("Received Message: " + msg);     } }
image from book

Notice that much of the code here is very similar to the code in the HelloWorldSender class. The interesting part here is the call to JmsTemplate.receive() in order to retrieve the message from the specified Destination. The receive() method has two other overloads besides the one that accepts Destination. The first of these accepts a String that is used to look up the Destination using the currently configured DestinationResolver. The second of these overloads accepts no arguments and uses the default Destination that is configured in the JmsTemplate instance.

The call to receive() blocks for the number of milliseconds shown by the getReceiveTimeout() method. By default, the timeout is set to –1, which indicates that there is no timeout and that receive() should wait indefinitely for a message. Because this class shares the same configuration as the HelloWorldSender class, no other configuration is required.

Now let's try this example out. First, make sure that ActiveMQ is running on your machine and then start up the HelloWorldReceiver class. This gives you the following message:

Will wait -1 seconds for message 

As we mentioned, the default timeout is set as –1 second, hence the message that is displayed. The HelloWorldReceiver class now sits patiently and waits for a message to go into the queue. All that remains is for you to start the HelloWorldSender and send the message into the queue. As soon as HelloWorldSender exits, notice that HelloWorldReceiver also exits, after writing a message similar to the following to stdout:

Received Message: ACTIVEMQ_TEXT_MESSAGE: dest = HelloWorldQueue, ¿ id = ID:meerkat-1747-1096023135651-10:0, text = Hello World

As you can see, you do not need very much code to get send and receive functionality up and running in your application. Using JmsTemplate, you can work in a JMS domain-agnostic manner, regardless of whether you are using JMS 1.0.2 or JMS 1.1. The send functionality described here is pretty typical of a standard JMS-based application, but in general, you will want to receive messages in an asynchronous manner; we cover this in more detail later. Before we do, however, we should look at how you can use publish/subscribe Destinations in your Spring JMS code.

Using Publish/Subscribe Messaging

You will not be surprised to learn that there really is not a vast amount of difference in your application code when you are using publish/subscribe Destinations rather than point-to- point Destinations. The main difference is in the application configuration because we need to configure a Topic rather than a Queue as the Destination for our messages. Listing 13-36 shows the configuration for this example.

Listing 13-36: Configuring a Topic

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>             <bean  >         <!-- Set the Destination Name -->         <constructor-arg index="0">             <value>FantasyFootballTopic</value>         </constructor-arg>     </bean> </beans>
image from book

Again we configure the ActiveMQ class appropriate to the Destination type directly in the configuration file rather than obtaining the object from a JNDI registry. The send part of this example (see Listing 13-37) is very similar to the previous example shown in Listing 13-34, except this time, we send multiple messages and use the convertAndSend() method to do so.

Listing 13-37: Sending Messages to a Topic

image from book
package com.apress.prospring.ch13.jms.pubsub;      import javax.jms.Destination;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.jms.core.JmsTemplate;      public class Producer {          public static void main(String[] args) throws Exception{         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-pubsub.xml"                      });              JmsTemplate template = (JmsTemplate) ctx.getBean("jmsTemplate");         final Destination destination = (Destination) ctx.getBean("destination");                  for(int x = 0; x < 2; x++) {             System.out.println("Sending Message: " + x);             template.convertAndSend(destination, "Foo: " + x);             Thread.sleep(400);         }     } }
image from book

You will recognize much of this code from the previous example (see Listing 13-34), but the interesting part here is inside the for loop. The convertAndSend() method is capable of converting Java objects into Message instances and sending them directly. To perform the conversion, convertAndSend() uses the instance of MessageConverter that is currently configured with the JmsTemplate. We discuss MessageConverters in more detail in the next section; for now, it is enough to know that the String we supply gets converted to a TextMessage instance. The call to Thread.sleep() simply slows the producer down, because we noticed that with these basic settings, the messages are being sent so fast that the consumers lose some arbitrarily.

The receive portion of this example, the Consumer class, is also very similar to the last example; the main difference here is that we put Consumer in an infinite loop, constantly waiting for new messages. Listing 13-38 shows the code for the Consumer class.

Listing 13-38: The Consumer Class

image from book
package com.apress.prospring.ch13.jms.pubsub;      import javax.jms.Destination; import javax.jms.Message;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.jms.core.JmsTemplate;      public class Consumer {          public static void main(String[] args) throws Exception {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-pubsub.xml"                      });              System.out.println("Starting Consumer");              JmsTemplate template = (JmsTemplate) ctx.getBean("jmsTemplate");         final Destination destination = (Destination) ctx                 .getBean("destination");              while (true) {             System.out.println("Waiting...");             Message msg = template.receive(destination);             System.out.println("Received: " + msg);         }     } }
image from book

At this point, nothing in this code should look unfamiliar. To test out this application, first start up three instances of the Consumer class; each instance should display the following message on screen:

Starting Consumer Waiting...

Next, run a single instance of the Producer class to send two messages to the publish/subscribe Destination. Once the Producer class exits, notice that all three of the running Consumer instances display something similar to this:

Received: ACTIVEMQ_TEXT_MESSAGE: dest = FantasyFootballTopic, ¿ id = ID:meerkat-1879-1096026129573-10:0, text = Foo: 0 Waiting... Received: ACTIVEMQ_TEXT_MESSAGE: dest = FantasyFootballTopic, ¿ id = ID:meerkat-1879-1096026129573-20:0, text = Foo: 1 Waiting...

As with the point-to-point example, there is not really much to say about this example. The code is extremely clear and simple to understand. However, notice that nothing in the code is specific to working with a publish/subscribe Destination. As we already stated, Spring provides this domain model-independent API regardless of whether you use JMS 1.1 or not.

A Note on MessageConverters

In the previous example, shown in Listing 13-37, we used the JmsTemplate.convertAndSend() method to convert a String to an instance of TextMessage and send it to a given Destination— all automatically. Behind the scenes, Spring uses an implementation of the MessageConverter interface to convert the Java object to an instance of Message that can be sent to the JMS provider. When receiving a message with the receiveAndConvert() method, which has overloads similar to receive(), Spring uses the MessageConverter to perform the reverse operation, transforming an instance of Message into an instance of an arbitrary object.

The default implementation of MessageConverter, SimpleMessageConverter, performs a variety of simple conversions both to and from Messages, including String to TextMessage and Map to MapMessage. You can find the full details of all the conversions performed by this class in the JavaDoc.

If you have a special requirement for your application, then you can create your own MessageConverter and configure it for a given JmsTemplate using the messageConverter property. As usual, it is best to configure this externally in the Spring configuration file.

Using JMS Local Transactions

In previous chapter, you might want to look at it now because we do not discuss the transaction architecture in any detail here.

To take advantage of Spring's transaction management functionality, first we need to wrap the JMS operations we want to perform inside a single method in a business object. We can then get Spring to create the transaction proxy of this business interface and send messages to a Destination as part of a transaction. Listing 13-39 shows the code for the Sender class that we use to highlight this functionality.

Listing 13-39: The Sender Class

image from book
package com.apress.prospring.ch13.jms.trans;      import org.springframework.jms.core.JmsTemplate;      public class Sender {          public static final String DESTINATION_NAME = "transDemo";          private JmsTemplate template;          public void setJmsTemplate(JmsTemplate template) {         this.template = template;     }          public void sendMessages() {         for(int x = 0; x < 10; x++) {             template.convertAndSend(DESTINATION_NAME, "bar");                          if(x == 8) {                 throw new RuntimeException("Ha Ha!");             }         }     } }
image from book

Most of this code should look familiar to you by now; however, there are two points of interest. First, notice that after nine messages are sent, a RuntimeException is thrown, which prevents the final message from being sent. When this code is running as part of a transaction, none of these messages should be in the queue once the transaction rolls back, but when this is executing outside of a transaction, the nine messages sent before the RuntimeException is thrown continue to exist in the queue.

Second, notice that we have used the overload of convertAndSend() that accepts a String instead of a Destination. When using this overload, Spring uses the instance of DestinationResolver that is configured for the JmsTemplate to locate the Destination object using the String value you supplied as the key. The exact meaning of the key differs between the different implementations of DestinationResolver; for instance, in the default implementation, DynamicDestinationResolver, the key is simply used as the Destination name. This is very useful when coupled with ActiveMQ, which creates the Destinations on the fly. The other implementation included with Spring is JndiDestinationResolver, which looks for Destination in JNDI— the key is the JNDI name that Spring should look under for the Destination.

The next step in this example is to create the configuration file. We want to demonstrate how to use the Sender class both inside and outside a transaction. To do this, we create a bean of instance Sender and then we create a transactional proxy to this bean. The code for this configuration is shown in Listing 13-40.

Listing 13-40: Configuring the Sender Class

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>             <bean  >         <property name="jmsTemplate">                <ref bean="jmsTemplate"/>         </property>     </bean>          <bean   >         <property name="connectionFactory">             <ref bean="connectionFactory"/>         </property>     </bean>          <bean   >          <property name="transactionManager">              <ref local="transactionManager"/>          </property>          <property name="target">              <ref local="senderTarget"/>          </property>          <property name="transactionAttributes">              <props>                  <prop key="send*">PROPAGATION_REQUIRED</prop>              </props>           </property>     </bean> </beans>
image from book

Much of the detail in this configuration file has already been discussed in this chapter or the last. The interesting point is that, because we are looking to manage JMS transactions, we use the JmsTransactionManager implementation of PlatformTransactionManager. As you saw previously, using JMS along with another resource provider in a distributed transaction requires JtaTransactionManager. Notice that when configuring the JmsTransactionManager, we pass it a reference of the ConnectionFactory so that it can manage transactions appropriately.

With the configuration in place, all that remains is to test both the transaction and nontransaction Sender beans. Listing 13-41 shows the code for this.

Listing 13-41: JMS Transactions in Action

image from book
package com.apress.prospring.ch13.jms.trans;      import javax.jms.Message;      import org.springframework.aop.support.AopUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.jms.core.JmsTemplate;      public class TransactionDemo {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-trans.xml"                      });              Sender transactional = (Sender) ctx.getBean("sender");         Sender nonTransactional = (Sender) ctx.getBean("senderTarget");              System.out.println("Trying Transactional...");         runTest(transactional, ctx);         System.out.println("Trying Non Transactional...");         runTest(nonTransactional, ctx);     }          private static void runTest(Sender sender, ApplicationContext ctx) {         System.out.println("Got Transactional Proxy?: "                 + AopUtils.isAopProxy(sender));              try {             sender.sendMessages();         } catch (Exception ex) {             System.out.println("Sender Threw Exception");         }              // now try to receive messages         JmsTemplate template = (JmsTemplate) ctx.getBean("jmsTemplate");              // set timeout so that eventually         // we stop trying to receive         template.setReceiveTimeout(2000);              Message msg = null;         int count = 0;              do {             msg = template.receive(Sender.DESTINATION_NAME);                  if (msg != null)                 count++;              } while (msg != null);              System.out.println("Received " + count + " messages.");     } }
image from book

Much of this code you have already seen in this chapter; the interesting part is inside the runTest() method. Once we have invoked Sender.sendMessages(), we attempt to retrieve any messages that are in the queue and count them to see if the transaction was applied correctly. By setting the timeout to 2 seconds, we ensure that the JmsTemplate.receive() method does not block forever and eventually, when there are no messages left, we get a count of the messages received. When running this code, we should see that for the first execution of runTest(), which uses the transactional Sender bean, the message count should be 0, because the whole sending process happened inside a transaction that was rolled back due to the RuntimeException thrown by the Sender class. When the runTest() method executes for the second time, this time with the nontransactional Sender bean, we should see a message count of nine, since nine messages are sent before the RuntimeException is raised.

Running the example proves this to be the case, as shown here:

Trying Transactional... Got Transactional Proxy?: true Sender Threw Exception Received 0 messages. Trying Non Transactional... Got Transactional Proxy?: false Sender Threw Exception Received 9 messages.

As you can see, the transaction kicked in as appropriate and caused a rollback when the RuntimeException was thrown, preventing the first nine messages sent from being committed to the queue. As this example shows, using JMS transactions with Spring is just as simple as using any other transactional resource supported by Spring. By using the declarative transaction features, you remove your need to manage transactions, and you can easily define transactional boundaries in terms of the methods on your business objects.

Receiving Messages Asynchronously

Earlier in this section, we looked at how you can send messages to a Destination and then receive them in a synchronous manner using JmsTemplate. The main drawback with this approach is that it doesn't accurately reflect the way JMS is used in most nontrivial applications— with asynchronous receive. As of version 1.1, the only support class for receiving messages asynchronously is the AbstractMessageDrivenBean class (covered in Chapter 12) that helps you build a message-driven EJB. When using a J2EE application server, message-driven beans are an excellent way to receive messages in an asynchronous manner, but when using a simple servlet container such as Tomcat, they obviously do not work.

Thankfully, the lack of support for asynchronous receive in Spring 1.1 is currently being addressed and the solution, from our tests, seems functionally complete. Currently available in the sandbox area of the Spring CVS repository is a selection of FactoryBean implementations. These are designed for use with JMS and make creating a JMS application with asynchronous receive simple. In this section, we preview these classes to see how they are used. Be aware that the classes discussed here are not yet finalized and may change before release. That said, they are already functionally complete, and we anticipate that the changes, if any, will be minimal.

To run the examples in this section, you need to check out the Spring source code from CVS. You can find instructions on doing so at http://sourceforge.net/cvs/?group_id=73357. Once you have the source code, you need to build both the main code branch and the sandbox code branch. You can do this by running the fulljar and sandboxjar targets in the Ant build script supplied with the code. Once you have the two JAR files, be sure to include them in your classpath so you can run the examples in this section.

Previewing Spring 1.2 JMS Support

The biggest addition to JMS support that Spring 1.2 will bring is a set of FactoryBeans that allow you to configure many more aspects of your JMS application in your configuration. One of the main features offered by this approach is that you can hook up a MessageListener implementation to a JMS Destination completely within your application configuration.

Before we look into the configuration of the different JMS components, we need a MessageListener implementation. Listing 13-42 shows this (very basic) implementation.

Listing 13-42: A Simple MessageListener Implementation

image from book
package com.apress.prospring.ch13.jms.asynch;      import javax.jms.Message; import javax.jms.MessageListener;      public class Listener implements MessageListener {     public void onMessage(Message msg) {         System.out.println("Received Message: " + msg);     } }
image from book

Here you can see that when a message is received, we simply write it to stdout. In a real application, you would, no doubt, perform much more processing than this, but for example purposes, this implementation is fine.

With the MessageListener implementation completed, we can now move on to configure the various JMS components. What we are looking for is a way to configure a MessageProducer that we can use to create messages and a MessageConsumer to link up our MessageListener. Before we can create either of these, we need a Session, and before we can create the Session, we need a Connection. Thankfully, FactoryBeans for all of these components are provided. Listing 13-43 shows the configuration for these different components.

Listing 13-43: Wiring JMS Components with Spring

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean  >         <!-- Set the Destination Name -->         <constructor-arg index="0">             <value>BarQueue</value>         </constructor-arg>     </bean>          <bean  >         <property name="connectionFactory">             <ref bean="connectionFactory"/>         </property>     </bean>          <bean  >         <property name="connection">             <ref local="connection"/>         </property>     </bean>          <bean  >         <property name="destination">             <ref local="destination"/>         </property>         <property name="session">             <ref local="session"/>         </property>     </bean>          <bean  >         <property name="destination">             <ref local="destination"/>         </property>         <property name="session">             <ref local="session"/>         </property>         <property name="messageListener">             <ref local="messageListener"/>         </property>     </bean>          <bean                   >         <property name="messageProducer">             <ref local="producer"/>         </property>         <property name="session">             <ref local="session"/>         </property>     </bean>          <bean                   /> </beans>
image from book

There are a number of beans in this configuration file, but the overall effect should be simple to understand. Using the connectionFactory bean from the common configuration file, we create a JMS Connection using the JmsConnectionFactoryBean. With this Connection, we create a Session using JmsSessionFactoryBean. From here, we create both a MessageProducer and a MessageConsumer using the appropriate FactoryBean, wiring in our MessageListener implementation to the MessageConsumer. Notice as well that we have configured an instance of the Producer class, the code for which is shown in Listing 13-44.

Listing 13-44: Sending Messages with MessageProducer

image from book
package com.apress.prospring.ch13.jms.asynch;      import javax.jms.Message; import javax.jms.MessageProducer; import javax.jms.Session;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      public class Producer {          private MessageProducer producer;     private Session session;          public static void main(String[] args) throws Exception{         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-asynch.xml"                      });                  Producer p = (Producer)ctx.getBean("messageProducer");         p.createMessages();         System.out.println("Done");     }          public void setMessageProducer(MessageProducer producer) {         this.producer = producer;     }          public void setSession(Session session) {         this.session = session;     }          public void createMessages() throws Exception{         for(int x = 0; x < 10; x++) {             Message m = session.createTextMessage("Foo " + x);             producer.send(m);             System.out.println("Sent Message: " + x);         }     } }
image from book

In this class, we use the MessageProducer and Session beans configured in Spring to send ten messages to a given Destination; the particular Destination is defined in the configuration for the MessageProducer bean. This covers the send part of the example. All that is required

at the receive end is a simple application that starts the JMS Connection, and then the MessageListener starts receiving messages sent to the queue. Listing 13-45 shows the code for the Consumer class that does just this.

Listing 13-45: The Consumer Class

image from book
package com.apress.prospring.ch13.jms.asynch;      import javax.jms.Connection;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      public class Consumer{          public static void main(String[] args) throws Exception{         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 new String[] { "./ch13/src/conf/applicationContext-common.xml",                         "./ch13/src/conf/applicationContext-asynch.xml"                      });                  Connection connection = (Connection)ctx.getBean("connection");         connection.start();                  System.out.println("Started");         System.in.read();     } }
image from book

There really isn't much to say about this class, other than if you do not start the Connection, the MessageListener will not receive any messages. To test this, first start up an instance of the Consumer class which, once started, displays the Started message and sits happily waiting for new messages. Once the Consumer class starts, run the Producer class a few times; each time you run it, your Consumer class receives a new set of messages.

JMS Summary

Current Spring support for JMS allows you to send messages quickly and easily in a manner that is independent of the underlying JMS domain model, be it point-to-point or publish/ subscribe. This is a feature that is already enjoyed by users of JMS 1.1–compliant providers, but Spring brings this functionality to JMS 1.0.2 users as well.

Using MessageConverters, you can support a complex conversion between JMS Messages and arbitrary Java types. Using the JmsTransactionManager, you can manage local JMS transactions using Spring's PlatformTransactionManager architecture. With features coming in Spring 1.2, you will be able to support full asynchronous messaging easily and without EJB.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net